Merge main: Guidad Felsökning flyttar ur roten till felsokning/
Main är sedan den här grenen skapades en helt annan produkt — Semantika,
en mobilapp med egen CDK-infrastruktur. Den äger nu repots rot: en
npm-workspaces-monorepo med apps/mobile, services/api och infra.
För att båda ska rymmas i samma repo flyttar Guidad Felsökning in i en
egen katalog i stället för att göra anspråk på roten:
felsokning/app webbklienten (Vite, egen package.json och
eslint-/vitest-konfiguration)
felsokning/services plattformstjänsten och AI-orkestern
felsokning/infra Terraform och databasschemat
felsokning/docs vision, moduler, drift
felsokning/supabase edge-funktion och migrationer
Merge:n hade tagit bort 128 filer som Guidad Felsökning bygger på —
värdapplikationens komponenter, Supabase-klienten, tillgångar — eftersom
main raderat dem och den här grenen inte råkat ändra just dem. De är
återställda på sin nya plats. Utan dem gick varken bygget eller
testerna: ai.ts och synk.ts importerar Supabase-klienten.
Semantikas rotfiler är orörda: package.json, eslint.config.js och
.github/workflows/ är deras. Guidad Felsökning har egna motsvarigheter i
sin katalog.
CI flyttar samtidigt från GitHub Actions till .gitea/workflows — samma
syntax, egna runners. .github/workflows/ tillhör Semantika härefter.
Verifierat på den nya platsen: 96 vitest-tester, typkontroll, eslint på
både klient och tjänster, bygge, och integrationstest mot riktig Postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -0,0 +1,1111 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Guidad Felsökning – plattforms-API
|
||||
version: 1.0.0
|
||||
description: |
|
||||
API-first-ytan för Guidad Felsökning: autentisering, organisationens
|
||||
användare, ärenden med append-only händelselogg, arbetsledaröversikt,
|
||||
publik Live Share-delning samt AI-orkestern.
|
||||
|
||||
**Händelseloggen är append-only.** Det finns inga update- eller
|
||||
delete-operationer — historik kan aldrig ändras eller raderas, vilket
|
||||
även garanteras av databastriggers. En händelse med redan känt `id`
|
||||
ignoreras tyst (idempotent synk).
|
||||
|
||||
**Multi-tenant.** All ärendedata är organisationsknuten. Ett ärende i
|
||||
en annan organisation ger `404`, som om det inte fanns.
|
||||
|
||||
Specen serveras live av tjänsten på `GET /api/openapi.yaml`.
|
||||
servers:
|
||||
- url: https://app.exempel.se
|
||||
description: Klustrets ingress (ersätt med er domän)
|
||||
tags:
|
||||
- name: Auth
|
||||
- name: Användare
|
||||
- name: Ärenden
|
||||
- name: Översikt
|
||||
- name: Delning
|
||||
- name: AI
|
||||
- name: Integrationer
|
||||
- name: Drift
|
||||
|
||||
paths:
|
||||
/halsa:
|
||||
get:
|
||||
tags: [Drift]
|
||||
summary: Hälsokontroll
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: Tjänsten svarar.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status: { type: string, example: ok }
|
||||
|
||||
/api/openapi.yaml:
|
||||
get:
|
||||
tags: [Drift]
|
||||
summary: Denna specifikation
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: OpenAPI-specen i YAML.
|
||||
content:
|
||||
application/yaml:
|
||||
schema: { type: string }
|
||||
|
||||
/api/auth/registrera:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Skapa organisation + systemadministratör
|
||||
description: >
|
||||
Registrerar en ny organisation (tenant) och dess första användare,
|
||||
som blir systemadministratör. Kan stängas av driften
|
||||
(REGISTRERING_OPPEN=false → 403).
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [epost, losenord, namn, organisation]
|
||||
properties:
|
||||
epost: { type: string, format: email }
|
||||
losenord: { type: string, minLength: 8 }
|
||||
namn: { type: string }
|
||||
organisation: { type: string }
|
||||
responses:
|
||||
"200": { $ref: "#/components/responses/Inloggad" }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
"409": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/auth/logga-in:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Logga in
|
||||
description: >
|
||||
Takt-begränsad i databasen, så spärren håller bakom flera
|
||||
repliker: 10 misslyckade försök per konto och 30 per källadress
|
||||
inom 15 minuter ger 429. Spärren gäller kontot även vid rätt
|
||||
lösenord — annars vore den meningslös. Ett avstängt konto svarar
|
||||
403.
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [epost, losenord]
|
||||
properties:
|
||||
epost: { type: string, format: email }
|
||||
losenord: { type: string }
|
||||
responses:
|
||||
"200": { $ref: "#/components/responses/Inloggad" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403":
|
||||
description: Kontot är avstängt.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
"429":
|
||||
description: För många misslyckade försök.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
|
||||
/api/anvandare:
|
||||
get:
|
||||
tags: [Användare]
|
||||
summary: Lista organisationens användare
|
||||
description: Kräver rollen `admin` eller `arbetsledare`.
|
||||
responses:
|
||||
"200":
|
||||
description: Användare i den egna organisationen.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
anvandare:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/Anvandare" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
post:
|
||||
tags: [Användare]
|
||||
summary: Skapa användare i den egna organisationen
|
||||
description: Kräver rollen `admin`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [epost, losenord, namn, roll]
|
||||
properties:
|
||||
epost: { type: string, format: email }
|
||||
losenord: { type: string, minLength: 8 }
|
||||
namn: { type: string }
|
||||
roll: { $ref: "#/components/schemas/Roll" }
|
||||
responses:
|
||||
"200":
|
||||
description: Den skapade användaren.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Anvandare" }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
"409": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/ecm/regler:
|
||||
get:
|
||||
tags: [Drift]
|
||||
summary: ECM Knowledge Library — aktuellt regelpaket
|
||||
description: >
|
||||
Serverdistribuerat, versionerat regelpaket (compliance-regler per
|
||||
ärendetyp, orsakskategorier, evidenskällor, undantagsorsaker).
|
||||
Uppdateras i driften utan appändring; klienten cachar och faller
|
||||
tillbaka till sitt inbyggda standardpaket offline.
|
||||
responses:
|
||||
"200":
|
||||
description: Regelpaketet.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
version: { type: string }
|
||||
arendetypRegler: { type: object, additionalProperties: true }
|
||||
undantagsorsaker: { type: array, items: { type: string } }
|
||||
orsakskategorier: { type: array, items: { type: string } }
|
||||
underlagskallor: { type: array, items: { type: string } }
|
||||
|
||||
/api/fordon/{identifierare}/historik:
|
||||
get:
|
||||
tags: [Ärenden]
|
||||
summary: Fordonshistorik — tidigare ärenden på samma objekt
|
||||
description: >
|
||||
Organisationens tidigare ärenden där objektet (regnr/VIN) matchar,
|
||||
med dokumenterade felorsaker. Underlag för pre-diagnostikens
|
||||
historiksteg och orsakskedjan.
|
||||
parameters:
|
||||
- name: identifierare
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Tidigare ärenden (senaste 20).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
arenden:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
avslutat: { type: boolean }
|
||||
felbeskrivning: { type: string, nullable: true }
|
||||
felorsaker: { type: array, items: { type: object, additionalProperties: true } }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/statistik/felorsaker:
|
||||
get:
|
||||
tags: [Översikt]
|
||||
summary: Felorsaksstatistik per orsakskategori
|
||||
description: >
|
||||
Flottdata ur dokumenterade felorsaksanalyser i organisationen.
|
||||
Kräver rollen `arbetsledare` eller `admin`.
|
||||
responses:
|
||||
"200":
|
||||
description: Antal per orsakskategori, fallande.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orsaker:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
orsak: { type: string }
|
||||
antal: { type: integer }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/anvandare/{anvandarId}/avaktivera:
|
||||
post:
|
||||
tags: [Användare]
|
||||
summary: Stäng av ett konto
|
||||
description: >
|
||||
Kräver rollen `admin` och att användaren tillhör samma
|
||||
organisation. Avstängningen höjer kontots token-version, så
|
||||
**pågående sessioner upphör omedelbart** — annars vore den
|
||||
verkningslös tills utfärdade tokens gick ut. Ett konto kan inte
|
||||
stänga av sig självt.
|
||||
parameters:
|
||||
- name: anvandarId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
responses:
|
||||
"200":
|
||||
description: Kontot är avstängt.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
namn: { type: string }
|
||||
aktiv: { type: boolean }
|
||||
"400":
|
||||
description: Försök att stänga av sitt eget konto.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/anvandare/{anvandarId}/aktivera:
|
||||
post:
|
||||
tags: [Användare]
|
||||
summary: Öppna ett avstängt konto
|
||||
description: >
|
||||
Kräver rollen `admin`. Kontot kan logga in igen, men tokens som
|
||||
återkallades vid avstängningen förblir ogiltiga.
|
||||
parameters:
|
||||
- name: anvandarId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
responses:
|
||||
"200": { description: Kontot är öppnat. }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/auth/logga-ut-alla:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Logga ut på alla enheter
|
||||
description: >
|
||||
Höjer den egna token-versionen, vilket gör samtliga utfärdade
|
||||
tokens för kontot ogiltiga direkt — vägen ut när en enhet
|
||||
tappats bort.
|
||||
responses:
|
||||
"200": { description: Alla sessioner är avslutade. }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/arenden/{arendeId}/bilagor:
|
||||
post:
|
||||
tags: [Ärenden]
|
||||
summary: Ladda upp en bilaga
|
||||
description: >
|
||||
Foton, videoklipp och instrumentbilder. Kroppen är råa bytes och
|
||||
`Content-Type` anger mediatypen — endast bild och video tas emot.
|
||||
Servern beräknar innehållets SHA-256 och returnerar en referens
|
||||
som ska läggas i händelsen; **hashen hamnar därmed i den
|
||||
append-only-skyddade loggen**, så en utbytt bild går att upptäcka.
|
||||
Innehållsadresserat: samma innehåll lagras en gång.
|
||||
parameters:
|
||||
- name: arendeId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
image/jpeg: { schema: { type: string, format: binary } }
|
||||
image/png: { schema: { type: string, format: binary } }
|
||||
image/webp: { schema: { type: string, format: binary } }
|
||||
video/mp4: { schema: { type: string, format: binary } }
|
||||
video/webm: { schema: { type: string, format: binary } }
|
||||
responses:
|
||||
"200":
|
||||
description: Referensen att spara i händelsen.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
hash: { type: string, description: "SHA-256 av innehållet, hex." }
|
||||
mediatyp: { type: string }
|
||||
storlek: { type: integer }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
"413":
|
||||
description: Bilagan är större än 32 MB.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
"415":
|
||||
description: Endast bilder och videoklipp tas emot.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
|
||||
/api/bilagor/{bilagaId}:
|
||||
get:
|
||||
tags: [Ärenden]
|
||||
summary: Hämta en bilaga
|
||||
description: >
|
||||
Organisationsknuten. Innehållet kontrolleras mot hashen innan det
|
||||
lämnas ut — stämmer det inte svarar tjänsten 409 i stället för att
|
||||
visa en bild som kan ha bytts ut.
|
||||
parameters:
|
||||
- name: bilagaId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Innehållet.
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema: { type: string, format: binary }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
"409":
|
||||
description: Innehållet stämmer inte med hashen i loggen.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
|
||||
/api/delad/{delningskod}/bilagor/{bilagaId}:
|
||||
get:
|
||||
tags: [Delning]
|
||||
summary: Hämta en bilaga via delningslänk
|
||||
description: >
|
||||
Samma filtrering som händelserna: bilagan lämnas bara ut om
|
||||
händelsen den hör till är synlig på delningens nivå. En bild som
|
||||
hör till en intern händelsetyp — t.ex. den skannade arbetsordern —
|
||||
nås alltså aldrig via kundlänken.
|
||||
security: []
|
||||
parameters:
|
||||
- name: delningskod
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
- name: bilagaId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Innehållet.
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema: { type: string, format: binary }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
"409": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/integrationer/leverantorer:
|
||||
get:
|
||||
tags: [Integrationer]
|
||||
summary: Registret över märkesspecifika kopplingar
|
||||
description: >
|
||||
Leverantörer är data, inte kod: registret läses ur
|
||||
`integrationer.json` (eller filen i `INTEGRATIONER_FIL`) och kan
|
||||
bytas via ConfigMap utan att applikationen byggs om. Innehåller
|
||||
endast fältdefinitioner — aldrig någon organisations uppgifter.
|
||||
responses:
|
||||
"200":
|
||||
description: Leverantörsdefinitioner.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
version: { type: string }
|
||||
leverantorer:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
namn: { type: string }
|
||||
beskrivning: { type: string }
|
||||
nyckeltyp:
|
||||
type: string
|
||||
enum: [vin, regnr]
|
||||
description: Vad uppslaget sker på. Utelämnat betyder VIN.
|
||||
falt:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
nyckel: { type: string }
|
||||
etikett: { type: string }
|
||||
hemlig:
|
||||
type: boolean
|
||||
description: >
|
||||
Hemliga fält maskeras alltid i svar och
|
||||
visas aldrig igen efter sparande.
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/integrationer:
|
||||
get:
|
||||
tags: [Integrationer]
|
||||
summary: Organisationens konfigurerade kopplingar
|
||||
description: >
|
||||
Kräver rollen `admin`. Uppgifterna lagras krypterade
|
||||
(AES-256-GCM) och returneras alltid maskerade — hemliga värden
|
||||
lämnar aldrig servern i klartext.
|
||||
responses:
|
||||
"200":
|
||||
description: Konfigurerade kopplingar med maskerade uppgifter.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
krypteringKonfigurerad:
|
||||
type: boolean
|
||||
description: Falskt om `INTEGRATION_NYCKEL` saknas — då kan inget sparas.
|
||||
integrationer:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
leverantor: { type: string }
|
||||
namn: { type: string }
|
||||
aktiv: { type: boolean }
|
||||
uppdaterad: { type: string, format: date-time }
|
||||
senast_testad: { type: string, format: date-time, nullable: true }
|
||||
senaste_status: { type: string, nullable: true }
|
||||
uppgifter:
|
||||
type: object
|
||||
additionalProperties: { type: string }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
post:
|
||||
tags: [Integrationer]
|
||||
summary: Spara eller uppdatera en kopplings uppgifter
|
||||
description: >
|
||||
Kräver rollen `admin`. Endast leverantörens definierade fält
|
||||
sparas och samtliga måste fyllas i. Uppgifterna krypteras innan
|
||||
de skrivs. Ett sparande nollställer tidigare testresultat.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [leverantor, uppgifter]
|
||||
properties:
|
||||
leverantor: { type: string }
|
||||
aktiv: { type: boolean, default: true }
|
||||
uppgifter:
|
||||
type: object
|
||||
additionalProperties: { type: string }
|
||||
responses:
|
||||
"200": { description: Sparad. }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
"503":
|
||||
description: Kryptering är inte konfigurerad (`INTEGRATION_NYCKEL` saknas).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
|
||||
/api/integrationer/{leverantor}:
|
||||
delete:
|
||||
tags: [Integrationer]
|
||||
summary: Ta bort en kopplings uppgifter
|
||||
description: Kräver rollen `admin`.
|
||||
parameters:
|
||||
- name: leverantor
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200": { description: Borttagen. }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/integrationer/{leverantor}/uppslag:
|
||||
post:
|
||||
tags: [Integrationer]
|
||||
summary: Slå upp ett fordon hos leverantören
|
||||
description: >
|
||||
Anropet görs alltid av servern — kundens leverantörsnycklar når
|
||||
aldrig webbläsaren. Svaret mappas till våra fält enligt
|
||||
registrets `svarsfalt`. Resultatet skrivs som kopplingens
|
||||
senaste teststatus.
|
||||
parameters:
|
||||
- name: leverantor
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [identifierare]
|
||||
properties:
|
||||
identifierare:
|
||||
type: string
|
||||
description: VIN eller registreringsnummer beroende på leverantörens `nyckeltyp`.
|
||||
responses:
|
||||
"200":
|
||||
description: Fordonsuppgifter från leverantören.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
fordon:
|
||||
type: object
|
||||
additionalProperties: { type: string }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404":
|
||||
description: Kopplingen är inte konfigurerad för organisationen.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
"502":
|
||||
description: Leverantören svarade med fel eller inga kända fält.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
"503":
|
||||
description: Kryptering är inte konfigurerad.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Fel" }
|
||||
|
||||
/api/delad/{delningskod}/beslut:
|
||||
post:
|
||||
tags: [Delning]
|
||||
summary: Kundens besked på ett åtgärdsförslag
|
||||
description: >
|
||||
Den enda skrivande publika vägen. Endast delningar på **kundnivå**
|
||||
som inte återkallats får svara, det måste finnas ett
|
||||
åtgärdsförslag, och **ett besked per ärende** — svaret kan inte
|
||||
ändras i efterhand. Takt-begränsad per delningskod. Beskedet
|
||||
loggas som `kundbeslut` med kanal `Delningslänk`.
|
||||
security: []
|
||||
parameters:
|
||||
- name: delningskod
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [beslut]
|
||||
properties:
|
||||
beslut: { type: string, enum: [godkant, avbojt, delvis] }
|
||||
kommentar: { type: string, maxLength: 500 }
|
||||
responses:
|
||||
"200":
|
||||
description: Beskedet registrerat.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ok: { type: boolean }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
"409": { $ref: "#/components/responses/Fel" }
|
||||
"429": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/organisation:
|
||||
get:
|
||||
tags: [Organisation]
|
||||
summary: Hämta organisationens namn och inställningar
|
||||
description: >
|
||||
Inställningarna styr vad som visas när ett ärende startas
|
||||
(objekttyper och identifieringsmetoder). Alla inloggade läser.
|
||||
responses:
|
||||
"200":
|
||||
description: Organisationen.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
namn: { type: string }
|
||||
installningar: { $ref: "#/components/schemas/OrganisationsInstallningar" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/organisation/installningar:
|
||||
post:
|
||||
tags: [Organisation]
|
||||
summary: Uppdatera organisationens inställningar
|
||||
description: Kräver rollen `admin`. Minst ett alternativ per lista.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/OrganisationsInstallningar" }
|
||||
responses:
|
||||
"200":
|
||||
description: Sparat.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ok: { type: boolean }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/arenden:
|
||||
get:
|
||||
tags: [Ärenden]
|
||||
summary: Lista organisationens ärenden
|
||||
responses:
|
||||
"200":
|
||||
description: Ärenden (senaste 200).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
arenden:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/Arende" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
post:
|
||||
tags: [Ärenden]
|
||||
summary: Registrera ett ärende (idempotent)
|
||||
description: >
|
||||
Skapar ärendet i användarens organisation. Ett redan känt `id`
|
||||
ignoreras tyst — ärenderaden ändras aldrig.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [id, nummer, skapad]
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
delningskod: { type: string, nullable: true }
|
||||
metodikId: { type: string, nullable: true }
|
||||
responses:
|
||||
"200":
|
||||
description: Mottaget.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ok: { type: boolean }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/arenden/{arendeId}/handelser:
|
||||
parameters:
|
||||
- name: arendeId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
get:
|
||||
tags: [Ärenden]
|
||||
summary: Hämta ärendets händelselogg
|
||||
responses:
|
||||
"200":
|
||||
description: Händelser i tidsordning.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
handelser:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/LoggPost" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
post:
|
||||
tags: [Ärenden]
|
||||
summary: Lägg till händelser (append-only, idempotent)
|
||||
description: >
|
||||
Lägger till händelser i loggen. Max 500 per anrop. En händelse med
|
||||
redan känt `id` ignoreras tyst — befintliga händelser skrivs aldrig
|
||||
över, och databastriggern stoppar alla ändringsförsök.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [handelser]
|
||||
properties:
|
||||
handelser:
|
||||
type: array
|
||||
maxItems: 500
|
||||
items: { $ref: "#/components/schemas/LoggPost" }
|
||||
responses:
|
||||
"200":
|
||||
description: Mottaget.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ok: { type: boolean }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/oversikt:
|
||||
get:
|
||||
tags: [Översikt]
|
||||
summary: Organisationsöversikt med status och statistikunderlag
|
||||
description: >
|
||||
Kräver rollen `arbetsledare` eller `admin`. Status, deltagande
|
||||
tekniker och sammanfattning härleds ur händelseloggen.
|
||||
responses:
|
||||
"200":
|
||||
description: Alla ärenden i organisationen med härledd status.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
arenden:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/OversiktsRad" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"403": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/arenden/{arendeId}/delningar:
|
||||
parameters:
|
||||
- name: arendeId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
get:
|
||||
tags: [Delning]
|
||||
summary: Lista ärendets delningslänkar
|
||||
responses:
|
||||
"200":
|
||||
description: Delningar (inklusive återkallade).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
delningar:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/Delning" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
post:
|
||||
tags: [Delning]
|
||||
summary: Skapa delningslänk med behörighetsnivå
|
||||
description: >
|
||||
Verkstaden kontrollerar alltid delningen: kund (det kunddelbara),
|
||||
partner (även hypoteser, tydligt märkta ej verifierade) eller
|
||||
intern (full insyn). Nivåfiltreringen sker på serversidan.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [niva]
|
||||
properties:
|
||||
niva: { $ref: "#/components/schemas/DelningsNiva" }
|
||||
responses:
|
||||
"200":
|
||||
description: Den skapade länkens kod.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
kod: { type: string }
|
||||
niva: { $ref: "#/components/schemas/DelningsNiva" }
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/delningar/{kod}/aterkalla:
|
||||
post:
|
||||
tags: [Delning]
|
||||
summary: Återkalla en delningslänk
|
||||
description: Efter återkallelse ger länken 404. Kan inte ångras.
|
||||
parameters:
|
||||
- name: kod
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
responses:
|
||||
"200":
|
||||
description: Återkallad.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ok: { type: boolean }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/delad/{delningskod}:
|
||||
get:
|
||||
tags: [Delning]
|
||||
summary: Publik Live Share-vy via delningskod
|
||||
description: >
|
||||
Kräver ingen inloggning — delningskoden är nyckeln. Filtreringen
|
||||
styrs av länkens behörighetsnivå: kund utesluter kategoribyten,
|
||||
hypoteser och AI-dialog; partner utesluter kategoribyten och
|
||||
AI-dialog; intern visar allt. Ärendets ursprungliga delningskod
|
||||
fungerar bakåtkompatibelt som kundnivå.
|
||||
security: []
|
||||
parameters:
|
||||
- name: delningskod
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, pattern: "^[A-Za-z0-9_-]+$" }
|
||||
responses:
|
||||
"200":
|
||||
description: Ärendet med nivåfiltrerad händelselogg.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
arende:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
handelser:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/LoggPost" }
|
||||
niva: { $ref: "#/components/schemas/DelningsNiva" }
|
||||
"404": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
/api/ai:
|
||||
post:
|
||||
tags: [AI]
|
||||
summary: AI-orkestern (separat tjänst bakom samma ingress)
|
||||
description: >
|
||||
Routas av ingressen till ai-orkester-tjänsten. Servern äger
|
||||
modellval, effort, systemprompt och svarsschema per uppgift:
|
||||
handledning (Claude Sonnet 5), granskning (Claude Opus 5),
|
||||
sammanfattning (Claude Sonnet 5), metodikval (Claude Haiku 4.5).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [uppgift, prompt]
|
||||
properties:
|
||||
uppgift:
|
||||
type: string
|
||||
enum: [handledning, granskning, sammanfattning, metodikval]
|
||||
prompt: { type: string, maxLength: 40000 }
|
||||
responses:
|
||||
"200":
|
||||
description: Klassificerat AI-svar med modellen som svarade.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
modell: { type: string, example: claude-sonnet-5 }
|
||||
svar:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/AiSvar"
|
||||
- type: object
|
||||
properties:
|
||||
metodikId:
|
||||
type: string
|
||||
enum: [vibration, elsystem, generisk]
|
||||
"400": { $ref: "#/components/responses/Fel" }
|
||||
"401": { $ref: "#/components/responses/Fel" }
|
||||
"502": { $ref: "#/components/responses/Fel" }
|
||||
"503": { $ref: "#/components/responses/Fel" }
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: >
|
||||
HS256-JWT från /api/auth/logga-in eller /api/auth/registrera.
|
||||
Anspråk: sub (användar-id), namn, org (organisations-id),
|
||||
roll, iat, exp (12 timmar).
|
||||
responses:
|
||||
Inloggad:
|
||||
description: Inloggad — token + kontouppgifter.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
token: { type: string }
|
||||
namn: { type: string }
|
||||
roll: { $ref: "#/components/schemas/Roll" }
|
||||
organisation: { type: string }
|
||||
Fel:
|
||||
description: Felsvar.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error: { type: string }
|
||||
schemas:
|
||||
Fel:
|
||||
type: object
|
||||
properties:
|
||||
error: { type: string }
|
||||
Roll:
|
||||
type: string
|
||||
enum: [tekniker, arbetsledare, admin]
|
||||
OrganisationsInstallningar:
|
||||
type: object
|
||||
required: [objekttyper, identifieringsmetoder]
|
||||
properties:
|
||||
objekttyper:
|
||||
type: array
|
||||
minItems: 1
|
||||
items: { type: string }
|
||||
identifieringsmetoder:
|
||||
type: array
|
||||
minItems: 1
|
||||
items: { type: string }
|
||||
DelningsNiva:
|
||||
type: string
|
||||
enum: [kund, partner, intern]
|
||||
Delning:
|
||||
type: object
|
||||
properties:
|
||||
kod: { type: string }
|
||||
niva: { $ref: "#/components/schemas/DelningsNiva" }
|
||||
skapad: { type: string, format: date-time }
|
||||
aterkallad: { type: string, format: date-time, nullable: true }
|
||||
Anvandare:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
epost: { type: string, format: email }
|
||||
namn: { type: string }
|
||||
roll: { $ref: "#/components/schemas/Roll" }
|
||||
Arende:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
delningskod: { type: string, nullable: true }
|
||||
metodik_id: { type: string, nullable: true }
|
||||
LoggPost:
|
||||
type: object
|
||||
required: [id, tidpunkt, anvandare, handelse]
|
||||
properties:
|
||||
id: { type: string }
|
||||
tidpunkt: { type: string, format: date-time }
|
||||
anvandare: { type: string }
|
||||
handelse: { $ref: "#/components/schemas/Handelse" }
|
||||
Handelse:
|
||||
type: object
|
||||
description: >
|
||||
Diskriminerad på `typ`; fullständiga fältdefinitioner i klientens
|
||||
domänmodell (src/felsokning/domain.ts).
|
||||
required: [typ]
|
||||
additionalProperties: true
|
||||
properties:
|
||||
typ:
|
||||
type: string
|
||||
enum:
|
||||
- objekt_identifierat
|
||||
- felbeskrivning
|
||||
- fraga_besvarad
|
||||
- kontroll_utford
|
||||
- observation
|
||||
- matvarde
|
||||
- hypotes
|
||||
- foto
|
||||
- kommentar
|
||||
- kategori_byte
|
||||
- inaktivitet_forklarad
|
||||
- overlamning
|
||||
- ansvarig_satt
|
||||
- arbetsorder_skannad
|
||||
- arendetyp_satt
|
||||
- historik_kontrollerad
|
||||
- matarstallning
|
||||
- video
|
||||
- atgardsforslag
|
||||
- kundbeslut
|
||||
- atgard_utford
|
||||
- kvalitetskontroll
|
||||
- reproducering
|
||||
- felorsak
|
||||
- export_skapad
|
||||
- ai_svar
|
||||
- arende_avslutat
|
||||
OversiktsRad:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
nummer: { type: integer }
|
||||
skapad: { type: string, format: date-time }
|
||||
delningskod: { type: string, nullable: true }
|
||||
metodik_id: { type: string, nullable: true }
|
||||
antal_handelser: { type: integer }
|
||||
forsta: { type: string, format: date-time, nullable: true }
|
||||
senaste: { type: string, format: date-time, nullable: true }
|
||||
avslutat: { type: boolean }
|
||||
objekt: { type: string, nullable: true }
|
||||
felbeskrivning: { type: string, nullable: true }
|
||||
tekniker:
|
||||
type: array
|
||||
items: { type: string }
|
||||
nullable: true
|
||||
AiSvar:
|
||||
type: object
|
||||
required: [rader, nastaSteg]
|
||||
properties:
|
||||
rader:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [typ, text]
|
||||
properties:
|
||||
typ:
|
||||
type: string
|
||||
enum: [observation, verifierat, hypotes, rekommendation]
|
||||
text: { type: string }
|
||||
nastaSteg: { type: string }
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
Reference in New Issue
Block a user