86efbaaca5
De åtta moduldokumenten och exempelflödet får engelska versioner. Kataloger och filnamn följer med: moduler/ → modules/, exempel/ → examples/, och de svenska filnamnen ersätts av engelska. Ett engelskt dokument i moduler/arendebrief.md hade varit inkonsekvent. Bytet gjordes med git mv så historiken följer med, och interna länkar i de svenska versionerna pekar nu på svenska syskon i stället för på filnamn som inte längre finns. Kodidentifierare och JSON-exempel står oöversatta även i de engelska versionerna — falt, hemlig, uppslag och svarsfalt är fältnamn i integrationer.json, inte prosa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
105 lines
4.5 KiB
Markdown
105 lines
4.5 KiB
Markdown
# Brand-specific integrations
|
|
|
|
> Canonical version. Swedish: [brand-integrations.sv.md](brand-integrations.sv.md).
|
|
> Code identifiers are Swedish and appear verbatim.
|
|
|
|
The workshop already has its contracts. The Volvo workshop has VIDA, the VAG
|
|
workshop has erWin, the independent workshop has a vehicle-data vendor. None of
|
|
them wants us to be the middleman for their subscription — and none of them has
|
|
the same set as the workshop next door.
|
|
|
|
That is why **the customer configures their own integrations** under
|
|
**Settings → Brand-specific integrations**, with their own credentials. We
|
|
provide the frame, not the account.
|
|
|
|
## Principles
|
|
|
|
**The credentials never reach the browser.** The same rule as for the
|
|
platform's own API keys: secrets live on the server. Credentials are encrypted
|
|
with AES-256-GCM before being written to the database, and the API returns
|
|
secret fields masked (`••••3456`). The client can see *that* an integration
|
|
exists and when it last worked — never what the key is.
|
|
|
|
**All lookups are performed by the server.** The client sends an identifier
|
|
(VIN or registration number); the server fetches the credentials, decrypts them
|
|
in memory, calls the vendor and returns only the mapped vehicle fields.
|
|
|
|
**Fail closed.** If the encryption key (`INTEGRATION_NYCKEL`) is missing,
|
|
nothing is saved — the API answers 503 and the settings page explains why. The
|
|
alternative, storing in plaintext "for now", does not exist.
|
|
|
|
**System administrator only.** Adding, changing and removing integrations
|
|
requires the `admin` role. A technician can read the register of available
|
|
vendors (otherwise the settings page cannot show them) but never any
|
|
organisation's credentials.
|
|
|
|
**Organisation-scoped.** Integrations belong to the organisation, just like case
|
|
data. No tenant sees another's.
|
|
|
|
## Vendors are data, not code
|
|
|
|
The register lives in `services/plattform/integrationer.json` and can be swapped
|
|
for a ConfigMap mount via `INTEGRATIONER_FIL`. A vendor is described entirely
|
|
declaratively:
|
|
|
|
```json
|
|
{
|
|
"id": "volvo_vida",
|
|
"namn": "Volvo VIDA",
|
|
"falt": [
|
|
{ "nyckel": "bas_url", "etikett": "Bas-URL (använd {vin} som platshållare)", "hemlig": false },
|
|
{ "nyckel": "api_nyckel", "etikett": "API-nyckel", "hemlig": true }
|
|
],
|
|
"uppslag": {
|
|
"urlFalt": "bas_url",
|
|
"auth": "header",
|
|
"authHeader": "X-Api-Key",
|
|
"authFalt": "api_nyckel",
|
|
"svarsfalt": { "marke": "make", "modell": "model", "arsmodell": "year" }
|
|
}
|
|
}
|
|
```
|
|
|
|
* `falt` (fields) — what the administrator has to fill in. `hemlig: true`
|
|
(secret) governs both encryption and masking.
|
|
* `uppslag.auth` (lookup auth) — `bearer`, `header`, `basic` or `query`. No
|
|
vendor-specific code branches; all variation lives in the register.
|
|
* `svarsfalt` (response fields) — mapping from the vendor's JSON (dot notation
|
|
supported) to our vehicle fields.
|
|
* `nyckeltyp: "regnr"` — the lookup is done on registration number instead of
|
|
VIN. `{vin}` / `{regnr}` in the URL template are substituted URL-encoded.
|
|
|
|
A new brand is therefore added by describing it — not by rebuilding the
|
|
application.
|
|
|
|
## What a lookup does and does not do
|
|
|
|
The lookup fills in the **vehicle description** (make, model, year, engine,
|
|
transmission). That is context data, not evidence: an answer from a vendor is
|
|
never a performed check and does not count in the
|
|
[evidence engine](evidence-engine.md). If the vendor returns no known fields,
|
|
the system says so plainly instead of showing empty rows.
|
|
|
|
Every lookup writes `senast_testad` (last tested) and `senaste_status` (last
|
|
status) on the integration. An expired subscription therefore shows up in
|
|
settings as an error message from the vendor, not as silently empty answers.
|
|
|
|
## API
|
|
|
|
| Route | Method | Role | What |
|
|
| --- | --- | --- | --- |
|
|
| `/api/integrationer/leverantorer` | GET | logged in | The register (field definitions, no credentials) |
|
|
| `/api/integrationer` | GET | admin | The organisation's integrations, secrets masked |
|
|
| `/api/integrationer` | POST | admin | Save/update credentials (encrypted) |
|
|
| `/api/integrationer/{leverantor}` | DELETE | admin | Remove |
|
|
| `/api/integrationer/{leverantor}/uppslag` | POST | logged in | Look up VIN/registration via the server |
|
|
|
|
Fully documented in `services/plattform/openapi.yaml`.
|
|
|
|
## Operations
|
|
|
|
`INTEGRATION_NYCKEL` is 32 bytes of hex or base64 (`openssl rand -hex 32`),
|
|
delivered via the secret `felsokning-hemligheter` — see
|
|
[OPERATIONS.md](../OPERATIONS.md). If the key is rotated, the integrations must
|
|
be saved again; the service then shows no values rather than guessing.
|