Refresh docs and fix Docker builds for current layout
READMEs: correct the auth model (PocketBase token relay, not JWT/sessions), document the full feature set (technical checks, fuel, maintenance, documents, reminders, attachments, integrations, OCPP charging control), the shipping built-in connectors (toyota, anker-solix), and the current endpoint surface. Docker: build against the current repo layout — Go 1.26, cmd/server entry point, Web App source under web/. Add the missing Web App Dockerfile (Go BFF) and .dockerignore, drop the obsolete AUTH_SECRET, modernise CORS var naming, and standardise on drivervault-* naming. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
f9b1bcc520
commit
1e76c2b7f9
@@ -3,7 +3,7 @@
|
||||
# --- Build stage -------------------------------------------------------------
|
||||
# Compile a static Go binary. The Vue panel is pre-built into internal/api/dist
|
||||
# and embedded via //go:embed, so no Node toolchain is needed here.
|
||||
FROM golang:1.22-alpine AS build
|
||||
FROM golang:1.26-alpine AS build
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
@@ -15,8 +15,9 @@ RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
# CGO_ENABLED=0 produces a static binary that runs on a bare alpine image.
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/api-server .
|
||||
# CGO_ENABLED=0 produces a static binary that runs on a bare alpine image. The
|
||||
# entry point is the cmd/server package.
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/api-server ./cmd/server
|
||||
|
||||
# --- Runtime stage -----------------------------------------------------------
|
||||
FROM alpine:latest
|
||||
|
||||
+66
-22
@@ -23,12 +23,17 @@ internal/
|
||||
│ ├── status.go # upstream health probes
|
||||
│ ├── health.go respond.go panel.go
|
||||
│ ├── cars.go records.go services.go parts.go shares.go me.go
|
||||
│ └── dist/ # built panel, embedded via go:embed
|
||||
│ ├── technical.go fuel.go maintenance.go documents.go reminders.go
|
||||
│ ├── attachments.go # one optional file per record (shared handlers)
|
||||
│ ├── integrations*.go # per-user Toyota / Anker Solix settings + OCPP control
|
||||
│ └── dist/ # built panel, embedded via go:embed
|
||||
├── config/config.go # env + .env load, .env write-back
|
||||
├── models/models.go # domain types + derived-field computation
|
||||
├── ocpp/ # OCPP 1.6J Central System (Anker Solix charging control)
|
||||
├── pb/client.go # PocketBase superuser client (runtime-retargetable)
|
||||
└── plugins/ # plugin system — see plugins/README.md
|
||||
├── plugin.go manager.go external.go doc.go
|
||||
└── builtin/ # built-in connectors (none yet)
|
||||
└── builtin/ # built-in connectors: toyota, ankersolix
|
||||
panel/ # Vue 3 + Tailwind panel source
|
||||
scripts/ # Node/Python maintenance scripts
|
||||
bin/api-server.exe # prebuilt binary the deployment runs
|
||||
@@ -85,14 +90,23 @@ other users `read` or `write` access. Every car/service/part handler is gated by
|
||||
|
||||
| Collection | Purpose | Key fields |
|
||||
|---|---|---|
|
||||
| `cars` | one per car | name, make, model, year, registration, vin, `currentKm`, `serviceIntervalDays` (365), `serviceIntervalKm` (15000), `technicalCheckIntervalDays` (365), `oilSpec`, `transmissionOilSpec`, `differentialOilSpec`, `brakeFluidSpec`, `coolantSpec`, `owner` |
|
||||
| `cars` | one per car | name, make, model, year, registration, `registrationCountry`, vin, `fuelType`, `buildDate`, `firstRegistrationDate`, `currentKm`, `serviceIntervalDays` (365), `serviceIntervalKm` (15000), `technicalCheckIntervalDays` (365), `oilSpec`, `transmissionOilSpec`, `differentialOilSpec`, `brakeFluidSpec`, `coolantSpec`, `owner` |
|
||||
| `service_records` | the service log | car, date, km, changed_oil, changed_engine_air_filter, changed_cabin_air_filter, notes |
|
||||
| `technical_checks` | roadworthiness inspections (przegląd techniczny / MOT / TÜV) | car, date, `result` (passed \| failed), cost, station, `valid_until`, notes |
|
||||
| `parts` | per-car parts catalog | car, name, part_number, category |
|
||||
| `parts` | per-car parts catalog | car, name, part_number, category, notes |
|
||||
| `fuel_entries` | refuelling log (efficiency derived on read) | car, date, km, liters, cost, `full_tank`, `missed_fill`, station, notes |
|
||||
| `maintenance_entries` | workshop visits & repairs (outside routine service) | car, date, km, type, status, workshop, parts_used, labor_cost, parts_cost, invoice_number, warranty_until, notes |
|
||||
| `car_documents` | paperwork (insurance, registration, road tax, …) | car, type, title, provider, reference, issue_date, expiry_date, cost, notes |
|
||||
| `reminders` | date/odometer reminders (some auto-derived) | car, title, type, due_date, due_km, repeat_days, repeat_km, done, done_at, notes |
|
||||
| `car_shares` | grants another user access to a car | car, user, `permission` (read \| write) |
|
||||
| `organizations` | tenants | name (unique) |
|
||||
| `control_audit` | OCPP control-command audit trail | user, charger, action, result, timestamp |
|
||||
| `users` | login + profile (built-in auth collection) | name, email, avatar, `role` (user \| admin \| superadmin), `organization`, bio, theme, locale, date_format, currency, font_size, deletion_requested_at |
|
||||
|
||||
Every record collection except `car_shares` / `organizations` / `control_audit`
|
||||
carries **one optional file attachment**, served only through the API Server
|
||||
(`GET /api/{records}/{id}/file`) — never a public PocketBase URL.
|
||||
|
||||
**Spreadsheet formulas** (from the original `Car Service.xlsx`), reproduced by
|
||||
the API on read:
|
||||
|
||||
@@ -118,12 +132,12 @@ GET /api/auth/me
|
||||
GET /api/identity
|
||||
|
||||
# current user (profile / appearance / avatar / data / account lifecycle)
|
||||
GET /api/me PATCH /api/me
|
||||
GET /api/me PATCH /api/me DELETE /api/me
|
||||
POST /api/me/password
|
||||
POST /api/me/avatar GET /api/me/avatar DELETE /api/me/avatar
|
||||
POST /api/me/verify/request
|
||||
GET /api/me/export POST /api/me/import
|
||||
POST /api/me/delete POST /api/me/delete/cancel DELETE /api/me
|
||||
POST /api/me/delete POST /api/me/delete/cancel
|
||||
|
||||
# users + organizations (admin or superadmin; org writes are superadmin-only)
|
||||
GET /api/users POST /api/users
|
||||
@@ -131,31 +145,47 @@ PATCH /api/users/{id} DELETE /api/users/{id}
|
||||
GET /api/orgs POST /api/orgs
|
||||
PATCH /api/orgs/{id} DELETE /api/orgs/{id}
|
||||
|
||||
# superadmin
|
||||
GET /api/admin/pb-config PUT /api/admin/pb-config
|
||||
POST /api/admin/pb-config/test
|
||||
# superadmin — connection + plugin management
|
||||
GET /api/admin/pb-config PUT /api/admin/pb-config POST /api/admin/pb-config/test
|
||||
GET /api/admin/webapp-config PUT /api/admin/webapp-config POST /api/admin/webapp-config/test
|
||||
GET /api/admin/plugins POST /api/admin/plugins
|
||||
GET /api/admin/plugins/{name} PUT /api/admin/plugins/{name}
|
||||
DELETE /api/admin/plugins/{name} POST /api/admin/plugins/{name}/health
|
||||
|
||||
# integrations (per-user plugin settings; superadmin → org admin → user cascade)
|
||||
GET /api/integrations/toyota PUT /api/integrations/toyota POST /api/integrations/toyota/health
|
||||
GET /api/integrations/toyota/vehicles
|
||||
GET /api/integrations/anker-solix PUT /api/integrations/anker-solix POST /api/integrations/anker-solix/health
|
||||
GET /api/integrations/anker-solix/chargers
|
||||
|
||||
# Anker Solix OCPP charging control (own/proxy mode + a live CSMS session)
|
||||
GET /api/integrations/anker-solix/chargers/{sn}/control
|
||||
POST /api/integrations/anker-solix/chargers/{sn}/control/token
|
||||
DELETE /api/integrations/anker-solix/chargers/{sn}/control/token
|
||||
POST /api/integrations/anker-solix/chargers/{sn}/{action}
|
||||
GET /ocpp/{serial} # charger dials in here (OCPP Basic auth, not bearer)
|
||||
|
||||
# cars + sharing
|
||||
GET /api/cars POST /api/cars
|
||||
GET /api/cars/{id} PATCH /api/cars/{id} DELETE /api/cars/{id}
|
||||
GET /api/cars/{id}/service-records
|
||||
GET /api/cars/{id}/parts
|
||||
GET /api/cars/{id}/shares POST /api/cars/{id}/shares
|
||||
DELETE /api/cars/{id}/shares/{userId}
|
||||
GET /api/cars/{id}/service-records GET /api/cars/{id}/technical-checks
|
||||
GET /api/cars/{id}/parts GET /api/cars/{id}/fuel-entries GET /api/cars/{id}/fuel-stats
|
||||
GET /api/cars/{id}/maintenance GET /api/cars/{id}/documents GET /api/cars/{id}/reminders
|
||||
GET /api/cars/{id}/shares POST /api/cars/{id}/shares DELETE /api/cars/{id}/shares/{userId}
|
||||
|
||||
# service records + parts
|
||||
GET /api/service-records POST /api/service-records
|
||||
GET /api/service-records/{id} PATCH /api/service-records/{id} DELETE /api/service-records/{id}
|
||||
GET /api/parts POST /api/parts
|
||||
GET /api/parts/{id} PATCH /api/parts/{id} DELETE /api/parts/{id}
|
||||
# per-record collections — each is GET(list) POST / GET PATCH DELETE {id}
|
||||
/api/service-records /api/technical-checks /api/parts
|
||||
/api/fuel-entries /api/maintenance /api/car-documents
|
||||
/api/reminders (+ POST /api/reminders/{id}/complete)
|
||||
|
||||
# attachments — one optional file per record, on every collection that takes one.
|
||||
# {records} = car-documents | service-records | technical-checks | maintenance | fuel-entries | parts
|
||||
POST /api/{records}/{id}/file GET /api/{records}/{id}/file DELETE /api/{records}/{id}/file
|
||||
```
|
||||
|
||||
`GET /api/cars` returns the caller's owned cars plus any shared with them, each
|
||||
annotated with an `access` field. `GET /api/service-records?car={id}` and
|
||||
`GET /api/parts?car={id}` filter by car.
|
||||
annotated with an `access` field. The per-record list endpoints also accept a
|
||||
`?car={id}` filter (e.g. `GET /api/service-records?car={id}`).
|
||||
|
||||
> **Gotcha:** `updateCar` rewrites **all** car columns from the payload, so a
|
||||
> `PATCH /api/cars/{id}` must send the **full** car object — omitted spec fields
|
||||
@@ -198,8 +228,22 @@ superadmin. Two kinds share one contract: **built-in** (Go, compiled in) and
|
||||
persists to `plugins.json`.
|
||||
|
||||
See **[`internal/plugins/README.md`](internal/plugins/README.md)** for the full
|
||||
guide. DriverVault ships no built-in connectors yet; the external kind is the
|
||||
place to start.
|
||||
guide. Two built-in connectors ship today — **Toyota Connected** (`toyota`,
|
||||
read-only MyToyota vehicle data) and the **Anker Solix** V1 EV charger
|
||||
(`anker-solix`) — and any number of external HTTP plugins can be registered at
|
||||
runtime with no rebuild.
|
||||
|
||||
### Integrations & charging control
|
||||
|
||||
Beyond the superadmin plugin registry, the built-in connectors are exposed
|
||||
per-user through `/api/integrations/*` under a **superadmin → org admin → user**
|
||||
cascade (each layer supplies defaults the next can override). For Anker Solix
|
||||
chargers the server additionally runs an **OCPP 1.6J Central System**
|
||||
(`internal/ocpp`): when the owner sets a control mode of own/proxy, the charger
|
||||
dials back in at `GET /ocpp/{serial}` (authenticated with OCPP Basic auth using a
|
||||
per-charger control token, not a bearer token) and the owner can start/stop and
|
||||
set charge limits, with every command rate-limited and written to a
|
||||
`control_audit` trail.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: carcontrol-api
|
||||
container_name: carcontrol-api
|
||||
image: drivervault-api
|
||||
container_name: drivervault-api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${PORT:-8080}:8080"
|
||||
@@ -13,8 +13,10 @@ services:
|
||||
PORT: "8080"
|
||||
# External PocketBase instance (all DB access goes through this server).
|
||||
PB_URL: "${PB_URL:-http://10.2.1.10:8027}"
|
||||
# Superuser service account. Leave unset and the server still starts — a
|
||||
# superadmin can configure it from the panel; management endpoints 503 until then.
|
||||
PB_ADMIN_EMAIL: "${PB_ADMIN_EMAIL}"
|
||||
PB_ADMIN_PASSWORD: "${PB_ADMIN_PASSWORD}"
|
||||
CORS_ORIGINS: "${CORS_ORIGINS:-http://localhost:5173}"
|
||||
AUTH_SECRET: "${AUTH_SECRET}"
|
||||
# Browser origins allowed by CORS (native apps are exempt).
|
||||
CORS_ALLOW_ORIGINS: "${CORS_ALLOW_ORIGINS:-http://localhost:5173}"
|
||||
AUTH_USERS_COLLECTION: "${AUTH_USERS_COLLECTION:-users}"
|
||||
|
||||
@@ -164,9 +164,10 @@ go build -o bin/api-server.exe ./cmd/server
|
||||
Restart the server. The plugin appears in the panel's **Plugins** card,
|
||||
**disabled** by default.
|
||||
|
||||
> DriverVault ships no built-in connectors yet, so `builtin/builtin.go` has an
|
||||
> empty import block. The **external** kind below needs no rebuild and is the
|
||||
> easier place to start.
|
||||
> DriverVault ships two built-in connectors today — `toyota` (Toyota Connected /
|
||||
> MyToyota, read-only vehicle data) and `anker-solix` (Anker Solix V1 EV charger)
|
||||
> — both blank-imported from `builtin/builtin.go`. The **external** kind below
|
||||
> needs no rebuild and is the easier place to start a new one.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// register them with the plugin registry. Import this package once (from the api
|
||||
// package) to make all built-in connectors available.
|
||||
//
|
||||
// DriverVault ships no built-in connectors yet — add one under
|
||||
// internal/plugins/builtin/<name>/ and blank-import it here, e.g.
|
||||
// Built-in connectors that ship today: toyota and ankersolix (imported below).
|
||||
// Add another under internal/plugins/builtin/<name>/ and blank-import it here, e.g.
|
||||
//
|
||||
// import _ "drivervault/apiserver/internal/plugins/builtin/acme"
|
||||
//
|
||||
// Until then, plugins are added at runtime as the "external" HTTP kind, which
|
||||
// needs no rebuild. See ../README.md.
|
||||
// Plugins can also be added at runtime as the "external" HTTP kind, which needs
|
||||
// no rebuild. See ../README.md.
|
||||
package builtin
|
||||
|
||||
import (
|
||||
|
||||
Reference in New Issue
Block a user