Files
werkbaum/backend/src/main/resources/openapi/api.yaml
T
mhoennigandClaude Fable 5 323e8fe0ba feat(taiga): Status zurückschreiben — zwei Knöpfe, niemand gewinnt von selbst (D91-Nachtrag 8, SPEC §9)
Weicht der Ticket-Status von der Statusbox ab, markiert das Knoten-Fenster
die Abweichung und bietet beide Richtungen ausdrücklich an.

- „nach Taiga schreiben": Spalte des Projekts suchen (Taiga schreibt nach Id,
  die Namen sind je Projekt frei) und mit der zuletzt GELESENEN `version`
  patchen — hat jemand dazwischen geändert, lehnt Taiga ab und der Text steht
  im Fenster, statt dass etwas überschrieben wird.
- „aus Taiga übernehmen": `setStatusBox()` schreibt die Box in die Textzeile,
  undo-fähig wie jede andere Änderung.
- Schreibbar sind nur die fünf abgebildeten Zustände; `[?]`, `[!]`, `[-]` und
  der neutrale Knoten lassen das Ticket unangetastet — mit Begründung im
  Fenster.
- Proxy: zwei Spaltenlisten (`/taiga/{userstory,task}-statuses?slug=`) und
  zwei Schreib-Endpunkte (`PATCH …/{ref}/status?slug=`); die Zielspalte wählt
  der Editor, das Backend parst die Notation nicht (D14).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 22:18:52 +02:00

1351 lines
43 KiB
YAML

openapi: 3.0.3
info:
title: Editor Backend API
description: |
CRUD-Grundgeruest fuer Dokumente.
Vorbereitete Erweiterungen (noch nicht aktiv):
- Autorisierung: securitySchemes.bearerAuth ist definiert, wird aber noch
auf keine Operation angewendet.
- Live-Editing: Das Feld `version` dient spaeter der Konflikterkennung
(Optimistic Locking) und als Basis fuer Delta-Updates via WebSocket.
- Clientseitige Verschluesselung: `content` ist ein opaker String. Der
Server interpretiert den Inhalt nicht, sodass spaeter Ciphertext
transportiert werden kann, ohne die API zu aendern.
version: 0.1.0
servers:
- url: /api/v1
tags:
- name: Documents
description: Verwaltung von Dokumenten
- name: Taiga
description: >
Schmaler, benannter Proxy zur konfigurierten Taiga-Instanz (D91).
Kein Durchreich-Proxy: Die Taiga-Basis-URL ist Server-Konfiguration
(`werkbaum.taiga.api-url`), nie Request-Parameter - die SSRF-Falle
naiver Proxies. Das Token bleibt im Browser; der Server speichert
nichts. Ohne konfigurierte Instanz antworten alle Taiga-Endpunkte
mit 503; ob sie konfiguriert ist, meldet `GET /info` (`taiga`).
paths:
/documents:
get:
tags: [Documents]
operationId: listDocuments
summary: Alle Dokumente auflisten
description: >
Verlangt das Master-Passwort (HTTP Basic, Benutzer `werkbaum`).
Ohne diesen Schutz waere jede Dokument-UUID auflistbar - und das
Zugriffsmodell "unerratbare UUID" damit hinfaellig. Nach mehreren
Fehlversuchen wird der Endpunkt fuer eine Weile gesperrt (429).
security:
- masterPassword: []
responses:
"200":
description: Liste aller Dokumente
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Document"
"401":
description: Master-Passwort fehlt oder ist falsch
"429":
description: Zu viele Fehlversuche; `Retry-After` nennt die Restdauer
post:
tags: [Documents]
operationId: createDocument
summary: Neues Dokument anlegen
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentCreateRequest"
responses:
"201":
description: Dokument wurde angelegt
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"400":
$ref: "#/components/responses/BadRequest"
/documents/{documentId}:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [Documents]
operationId: getDocument
summary: Einzelnes Dokument abrufen
responses:
"200":
description: Das angeforderte Dokument
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Documents]
operationId: updateDocument
summary: Dokument vollstaendig aktualisieren
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentUpdateRequest"
responses:
"200":
description: Aktualisiertes Dokument
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: Versionskonflikt (fuer spaeteres Optimistic Locking reserviert)
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
delete:
tags: [Documents]
operationId: deleteDocument
summary: Dokument loeschen
responses:
"204":
description: Dokument wurde geloescht
"404":
$ref: "#/components/responses/NotFound"
/documents/{documentId}/history:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [Documents]
operationId: getDocumentHistory
summary: Historie eines Dokuments abrufen
description: >
Liefert die nutzersichtbaren Staende (Meilensteine) in chronologischer
Reihenfolge, dazu immer den juengsten Stand. Kurzlebige Sync-Versionen
des Live-Editings bleiben aussen vor. Die Historie ueberlebt das
Loeschen des Dokuments.
responses:
"200":
description: Meilensteine des Dokuments (aelteste zuerst)
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/DocumentHistoryEntry"
"404":
$ref: "#/components/responses/NotFound"
/documents/{documentId}/restore:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
post:
tags: [Documents]
operationId: restoreDocument
summary: Geloeschtes Dokument aus der Historie wiederherstellen
description: >
Stellt ein geloeschtes Dokument unter derselben UUID wieder her.
Ohne Request-Body wird der letzte Stand vor dem Loeschen
wiederhergestellt; optional kann eine bestimmte Version angegeben
werden (auch als Rollback fuer ein noch existierendes Dokument).
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/RestoreRequest"
responses:
"200":
description: Wiederhergestelltes Dokument
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: >
Dokument existiert noch und es wurde keine Zielversion angegeben.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
/documents/{documentId}/content:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
patch:
tags: [Documents]
operationId: patchDocumentContent
summary: Zeilen-Diff auf den Inhalt anwenden (Live-Editing)
description: >
Reicht eine Aenderung als Zeilen-Diff gegen eine Basisversion ein.
Ist die Basis veraltet, ueberschneiden sich die Operationen aber nicht
mit den zwischenzeitlichen, verschiebt der Server sie selbst und
akzeptiert (200); die fremden Operationen stehen dann in
`opsSinceBase`, damit der Client seine Schattenkopie nachzieht. Nur
bei echter Ueberschneidung antwortet er mit 409.
`clientId` und `seq` machen den Aufruf wiederholbar: Eine Wiederholung
liefert das Ergebnis von damals, statt die Aenderung ein zweites Mal
anzuwenden.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ContentPatchRequest"
responses:
"200":
description: Aenderung angenommen (ggf. serverseitig verschoben)
content:
application/json:
schema:
$ref: "#/components/schemas/ContentPatchResult"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: >
Echte Ueberschneidung mit einer zwischenzeitlichen Aenderung. Der
Client entscheidet - fremde Fassung uebernehmen oder eigene
durchsetzen -, ohne neu zu laden.
content:
application/json:
schema:
$ref: "#/components/schemas/ContentConflict"
"422":
description: >
Diff nicht anwendbar: Pruefsummenfehler, Index ausserhalb, oder die
Basisversion ist nicht mehr verfuegbar. Der Client laedt einmalig neu.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
/documents/{documentId}/title:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
patch:
tags: [Documents]
operationId: patchDocumentTitle
summary: Dokument umbenennen (Live-Editing)
description: >
Aendert nur den Titel - er ist ein Metadatum, kein Zeileninhalt, und
bekommt deshalb seinen eigenen Weg mit Versionspruefung (D76). Die
Umbenennung erzeugt eine neue Version vom Typ RENAMED, die der
Aenderungsfeed mit dem neuen Titel im Klartext zustellt - alle sehen
denselben Namen.
Verwaltungs-Aktion: Sie wird kuenftig an das geplante Owner-Passwort
gebunden (die Bindung kommt als Berechtigungspruefung dazu, die
Signatur bleibt).
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TitlePatchRequest"
responses:
"200":
description: Umbenanntes Dokument
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: >
Versionskonflikt - `expectedVersion` ist nicht mehr die aktuelle
Version. Der Client holt das Dokument frisch und versucht es mit
dessen Version erneut.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
/documents/{documentId}/changes:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [Documents]
operationId: getDocumentChanges
summary: Aenderungsfeed (Long Polling)
description: >
Liefert alles, was seit `since` geschehen ist. Gibt es nichts, haelt
der Server die Anfrage bis zu `wait` Sekunden offen und antwortet
sofort, sobald eine Aenderung eintrifft; sonst 204.
Der Feed arbeitet auf der **Historie**, nicht am Dokument: Ein
geloeschtes Dokument muss sein DELETED-Ereignis noch zustellen koennen.
404 gibt es deshalb nur bei gaenzlich unbekannter UUID.
Ist `since` bereits verdichtet, kann kein exaktes Diff mehr geliefert
werden - dann enthaelt die Antwort statt `ops` den **Volltext**, und
`fromVersion` fehlt. Der Client ersetzt seinen Stand.
parameters:
- name: since
in: query
required: true
description: Zuletzt gesehene Version; 0 fuer "noch nichts".
schema:
type: integer
format: int64
minimum: 0
- name: wait
in: query
required: false
description: >
Wartezeit in Sekunden. Serverseitig geklemmt - ein Client darf
keine beliebig lange Verbindung binden.
schema:
type: integer
format: int32
minimum: 0
default: 25
responses:
"200":
description: Aenderungen seit `since`
content:
application/json:
schema:
$ref: "#/components/schemas/ChangeFeed"
"204":
description: Nichts Neues innerhalb der Wartezeit
"404":
$ref: "#/components/responses/NotFound"
/taiga/auth:
post:
tags: [Taiga]
operationId: taigaLogin
summary: Bei Taiga anmelden (Proxy)
description: >
Reicht Benutzername und Passwort einmalig an die konfigurierte
Taiga-Instanz durch (`POST <api-url>/auth`, mit dem serverseitig
konfigurierten Login-Typ, Voreinstellung `ldap` - D91-Nachtrag 1).
Der Endpunkt sieht das Passwort nur im Durchflug: Der Server
speichert nichts und loggt den Request-Body nie; das Token gehoert
dem Browser. Bei der angekuendigten OIDC-Umstellung der Instanz wird
dieser Endpunkt durch den Redirect-Flow ersetzt - die uebrigen
Taiga-Endpunkte bleiben unveraendert (sie nehmen nur das Token).
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaAuthRequest"
responses:
"200":
description: Anmeldung gelungen
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaSession"
"400":
description: >
Zugangsdaten abgelehnt - Taiga meldet falsche Anmeldedaten als
400, der Status wird durchgereicht.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/projects:
get:
tags: [Taiga]
operationId: taigaProjects
summary: Projekte des angemeldeten Nutzers auflisten (Proxy)
description: >
`GET <api-url>/projects?member=<userId>` - die Auswahlliste des
Projekt-Dialogs der Ticket-Anlage. Der `slug` ist zugleich der Wert
des Schlagworts `&taiga.<slug>` (SPEC par. 1).
parameters:
- $ref: "#/components/parameters/TaigaToken"
- name: member
in: query
required: true
description: Taiga-Benutzer-Id aus der Sitzung; filtert auf die eigenen Projekte.
schema:
type: integer
format: int64
responses:
"200":
description: Projekte des Nutzers
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TaigaProject"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/userstories:
post:
tags: [Taiga]
operationId: taigaCreateStory
summary: User Story anlegen (Proxy)
description: >
`POST <api-url>/userstories`. Die Antwort traegt die projektweite
`ref` - Werkbaum schreibt daraus `#US-<ref>` als Token an die
Knotenzeile (SPEC par. 11, D91-Nachtrag 2).
parameters:
- $ref: "#/components/parameters/TaigaToken"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaStoryCreateRequest"
responses:
"201":
description: Story wurde angelegt
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaTicket"
"400":
$ref: "#/components/responses/BadRequest"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/tasks:
post:
tags: [Taiga]
operationId: taigaCreateTask
summary: Task unter einer User Story anlegen (Proxy)
description: >
`POST <api-url>/tasks`. Tasks haengen immer an einer Story
(`userStory` ist die Id, nicht die Ref) - storyless Tasks sind im
Kanban unsichtbar und werden bewusst nicht angeboten (D91). Die
Antwort traegt die `ref`; Werkbaum schreibt daraus `#T-<ref>`.
parameters:
- $ref: "#/components/parameters/TaigaToken"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaTaskCreateRequest"
responses:
"201":
description: Task wurde angelegt
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaTicket"
"400":
$ref: "#/components/responses/BadRequest"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/userstories/{ref}:
get:
tags: [Taiga]
operationId: taigaStoryByRef
summary: User Story ueber ihre Ref lesen (Proxy)
description: >
Loest `#US-<ref>` auf (D91-Nachtrag 6): Betreff, Status und
Zustaendiger einer Story. Refs sind nur **je Projekt** eindeutig -
deshalb der `slug`, den der Editor aus dem Schlagwort
`&taiga.<slug>` des Teilbaums nimmt (SPEC par. 1). Der Proxy fragt
dafuer zuerst `GET <api-url>/projects/by_slug`, dann
`GET <api-url>/userstories/by_ref?project=<id>&ref=<ref>`.
Nur gelesen: Werkbaum schreibt hier nichts nach Taiga und nichts in
den Notationstext.
parameters:
- $ref: "#/components/parameters/TaigaToken"
- $ref: "#/components/parameters/TaigaRef"
- $ref: "#/components/parameters/TaigaSlug"
responses:
"200":
description: Die Story
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaTicketDetail"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"404":
description: >
Projekt oder Ref gibt es nicht - Taigas Status wird
durchgereicht (der Editor zeigt den Fehlertext im Knoten-Fenster).
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/tasks/{ref}:
get:
tags: [Taiga]
operationId: taigaTaskByRef
summary: Task ueber ihre Ref lesen (Proxy)
description: >
Wie `GET /taiga/userstories/{ref}`, nur ueber
`GET <api-url>/tasks/by_ref` - loest `#T-<ref>` auf. Zwei Endpunkte
statt eines mit Typ-Parameter, weil das Praefix der Ref den Typ
traegt und Taiga getrennte `by_ref`-Endpunkte hat (SPEC par. 11).
parameters:
- $ref: "#/components/parameters/TaigaToken"
- $ref: "#/components/parameters/TaigaRef"
- $ref: "#/components/parameters/TaigaSlug"
responses:
"200":
description: Die Task
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaTicketDetail"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"404":
description: Projekt oder Ref gibt es nicht
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/userstories/{ref}/status:
patch:
tags: [Taiga]
operationId: taigaSetStoryStatus
summary: Status einer User Story setzen (Proxy)
description: >
Schreibt den Status zurueck (D91-Nachtrag 7/8) - die eine Haelfte des
Abgleichs, die der Benutzer im Knoten-Fenster ausdruecklich anstoesst;
von selbst geschieht nichts. `status` ist die **Id** einer Spalte aus
`GET /taiga/userstory-statuses` (die Namen sind je Projekt frei),
`version` die zuletzt gelesene: Hat jemand dazwischen geaendert, lehnt
Taigas optimistische Sperre ab und der Konflikt wird durchgereicht,
statt ihn zu ueberschreiben.
parameters:
- $ref: "#/components/parameters/TaigaToken"
- $ref: "#/components/parameters/TaigaRef"
- $ref: "#/components/parameters/TaigaSlug"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaStatusPatch"
responses:
"200":
description: Der neue Stand des Tickets
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaTicketDetail"
"400":
$ref: "#/components/responses/BadRequest"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"404":
description: Projekt oder Ref gibt es nicht
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/tasks/{ref}/status:
patch:
tags: [Taiga]
operationId: taigaSetTaskStatus
summary: Status einer Task setzen (Proxy)
description: >
Wie `PATCH /taiga/userstories/{ref}/status`, nur fuer Tasks; die
Spalten kommen aus `GET /taiga/task-statuses`.
parameters:
- $ref: "#/components/parameters/TaigaToken"
- $ref: "#/components/parameters/TaigaRef"
- $ref: "#/components/parameters/TaigaSlug"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaStatusPatch"
responses:
"200":
description: Der neue Stand des Tickets
content:
application/json:
schema:
$ref: "#/components/schemas/TaigaTicketDetail"
"400":
$ref: "#/components/responses/BadRequest"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"404":
description: Projekt oder Ref gibt es nicht
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/userstory-statuses:
get:
tags: [Taiga]
operationId: taigaStoryStatuses
summary: Workflow-Spalten der Storys eines Projekts (Proxy)
description: >
`GET <api-url>/userstory-statuses?project=<id>`. Gebraucht zum
Schreiben: Taiga nimmt die Status-**Id**, und welche Spalte zu welcher
Statusbox gehoert, entscheidet der Editor (SPEC par. 4/9) - das
Backend parst die Notation nicht (D14).
parameters:
- $ref: "#/components/parameters/TaigaToken"
- $ref: "#/components/parameters/TaigaSlug"
responses:
"200":
description: Die Spalten des Projekts
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TaigaStatus"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"404":
description: Projekt gibt es nicht
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/taiga/task-statuses:
get:
tags: [Taiga]
operationId: taigaTaskStatuses
summary: Workflow-Spalten der Tasks eines Projekts (Proxy)
description: Wie `GET /taiga/userstory-statuses`, nur fuer Tasks.
parameters:
- $ref: "#/components/parameters/TaigaToken"
- $ref: "#/components/parameters/TaigaSlug"
responses:
"200":
description: Die Spalten des Projekts
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TaigaStatus"
"401":
description: Token fehlt oder ist abgelaufen
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"404":
description: Projekt gibt es nicht
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
"502":
$ref: "#/components/responses/TaigaUnavailable"
"503":
$ref: "#/components/responses/TaigaNotConfigured"
/info:
get:
tags: [Documents]
operationId: getInfo
summary: Name und Version des Dienstes
description: >
Offen und ohne Nebenwirkung - gedacht als Lebendprobe fuer Deploy und
Ueberwachung. Ohne diesen Endpunkt bliebe dafuer nur eine Anfrage nach
einem Dokument, das es nicht gibt, und man muesste auf eine 404 hoffen:
Ein erwarteter Fehler ist eine schlechte Zusicherung, weil ihn auch ein
falsch konfigurierter Proxy liefert.
responses:
"200":
description: Der Dienst laeuft
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceInfo"
components:
parameters:
TaigaToken:
name: X-Taiga-Token
in: header
required: true
description: >
Das Taiga-Token aus `POST /taiga/auth`, nackt (ohne `Bearer `-Praefix).
Der Proxy setzt daraus den `Authorization: Bearer <token>`-Header der
Weiterleitung. Bewusst ein eigener Header-Name: Einen Header-Parameter
namens `Authorization` muessen OpenAPI-Werkzeuge laut Spezifikation
ignorieren, und der Name kollidierte mit dem Master-Passwort (Basic).
schema:
type: string
maxLength: 512
TaigaRef:
name: ref
in: path
required: true
description: >
Die nackte Taiga-Nummer, ohne das Werkbaum-Praefix: aus `#US-123`
bzw. `#T-1234` wird `123` bzw. `1234`. Den Typ traegt der Pfad.
schema:
type: integer
format: int64
minimum: 1
TaigaSlug:
name: slug
in: query
required: true
description: >
Projekt-Slug aus dem Schlagwort `&taiga.<slug>` (SPEC par. 1). Refs
laufen je Projekt fortlaufend - ohne das Projekt ist eine Ref nicht
eindeutig.
schema:
type: string
minLength: 1
maxLength: 255
responses:
TaigaNotConfigured:
description: >
Keine Taiga-Instanz konfiguriert (`werkbaum.taiga.api-url`) - der
Editor fragt vorher `GET /info` (`taiga`) und zeigt die Aktionen
dann gar nicht erst.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
TaigaUnavailable:
description: Taiga-Instanz nicht erreichbar oder antwortet fehlerhaft
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
NotFound:
description: Ressource nicht gefunden
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
BadRequest:
description: Ungueltige Anfrage
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
schemas:
Document:
type: object
required: [id, title, content, version, createdAt, updatedAt]
properties:
id:
type: string
format: uuid
readOnly: true
title:
type: string
maxLength: 255
content:
type: string
description: >
Opaker Inhalt. Bei clientseitiger Verschluesselung enthaelt dieses
Feld spaeter den Ciphertext.
version:
type: integer
format: int64
description: Wird bei jeder Aenderung inkrementiert (Basis fuer Live-Editing/Konflikterkennung).
createdAt:
type: string
format: date-time
readOnly: true
updatedAt:
type: string
format: date-time
readOnly: true
DocumentCreateRequest:
type: object
required: [title, content]
properties:
title:
type: string
minLength: 1
maxLength: 255
content:
type: string
DocumentUpdateRequest:
type: object
required: [title, content]
properties:
title:
type: string
minLength: 1
maxLength: 255
content:
type: string
expectedVersion:
type: integer
format: int64
description: Optional; wird spaeter fuer Optimistic Locking ausgewertet.
DocumentHistoryEntry:
description: >
Ein Stand der nutzersichtbaren Historie. Sync-Versionen des
Live-Editings erscheinen hier nicht - sie tragen das Protokoll, nicht
die Erzaehlung.
type: object
required: [documentId, version, title, content, changeType, timestamp]
properties:
documentId:
type: string
format: uuid
version:
type: integer
format: int64
title:
type: string
content:
type: string
changeType:
type: string
description: >
RESTORED heisst: ein geloeschtes Dokument ist wieder da.
ROLLED_BACK ist der Rueckfall eines lebenden Dokuments auf eine
aeltere Version.
enum: [CREATED, UPDATED, RENAMED, DELETED, RESTORED, ROLLED_BACK]
timestamp:
type: string
format: date-time
clientId:
type: string
description: Fehlt bei Aenderungen ohne Absender (etwa ueber PUT).
displayName:
type: string
description: >
Selbstgewaehlt und ohne Anmeldung eine Behauptung - in der
Oberflaeche nicht wie ein Nachweis darstellen. Traegt das
"geaendert von" der Historie (D86).
TitlePatchRequest:
type: object
required: [title, expectedVersion]
properties:
title:
type: string
minLength: 1
maxLength: 255
expectedVersion:
type: integer
format: int64
description: >
Die Version, auf der die Umbenennung aufsetzt - weicht sie von der
aktuellen ab, antwortet der Server mit 409.
RestoreRequest:
type: object
properties:
version:
type: integer
format: int64
description: >
Optionale Zielversion. Ohne Angabe wird der letzte inhaltliche
Stand vor dem Loeschen wiederhergestellt.
LineOperation:
description: >
Eine Zeilen-Operation relativ zur Basisversion (0-basierter Index).
Operationen sind aufsteigend sortiert und ueberschneiden sich nicht.
type: object
required: [op, index]
properties:
op:
type: string
enum: [replace, insert, delete]
index:
type: integer
format: int32
minimum: 0
count:
type: integer
format: int32
minimum: 0
description: Zahl der betroffenen Basiszeilen; bei `insert` ohne Bedeutung.
lines:
type: array
description: Einzusetzende Zeilen; bei `delete` ohne Bedeutung.
items:
type: string
ContentPatchRequest:
type: object
required: [baseVersion, checksum, clientId, seq, ops]
properties:
baseVersion:
type: integer
format: int64
description: Version, gegen die das Diff gebildet wurde.
checksum:
type: string
description: >
Pflicht. `sha256:<hex>` des vollstaendigen Basistexts mit
LF-Zeilenenden. Die Versionsnummer bestaetigt nur, dass die Basis
dieselbe Version ist, nicht dass beide Seiten sie gleich lesen.
clientId:
type: string
maxLength: 64
description: Zufaellige, pseudonyme Kennung des Clients.
seq:
type: integer
format: int64
description: Laufende Nummer dieses Clients; macht den Aufruf wiederholbar.
displayName:
type: string
maxLength: 64
description: >
Selbstgewaehlter Anzeigename. Ohne Anmeldung ist er eine
Behauptung und darf in der Oberflaeche nicht wie ein Nachweis
aussehen.
milestone:
type: boolean
default: false
description: >
`true` schreibt einen nutzersichtbaren Stand (Knopfdruck).
Der getaktete Strom laesst das Feld weg.
ops:
type: array
items:
$ref: "#/components/schemas/LineOperation"
ContentPatchResult:
type: object
required: [version, opsSinceBase]
properties:
version:
type: integer
format: int64
description: Neue Version des Dokuments.
opsSinceBase:
type: array
description: >
Leer, wenn die Basis noch aktuell war. Sonst die fremden
Operationen, um die der Server verschoben hat.
items:
$ref: "#/components/schemas/LineOperation"
ContentConflict:
type: object
required: [currentVersion, opsSinceBase]
properties:
currentVersion:
type: integer
format: int64
opsSinceBase:
type: array
description: Diff von der eingereichten Basis bis zur aktuellen Version.
items:
$ref: "#/components/schemas/LineOperation"
ChangeEvent:
description: Was an einer Version geschehen ist und wer sie eingereicht hat.
type: object
required: [version, changeType]
properties:
version:
type: integer
format: int64
changeType:
type: string
enum: [CREATED, UPDATED, RENAMED, DELETED, RESTORED, ROLLED_BACK]
clientId:
type: string
description: Fehlt bei Aenderungen ohne Absender (etwa ueber PUT).
displayName:
type: string
description: >
Selbstgewaehlt und ohne Anmeldung eine Behauptung - in der
Oberflaeche nicht wie ein Nachweis darstellen.
title:
type: string
description: >
Nur bei RENAMED - der neue Titel im Klartext, damit der Client
ihn ohne weiteren Abruf uebernehmen kann (D76).
ChangeFeed:
type: object
required: [currentVersion, events]
properties:
fromVersion:
type: integer
format: int64
description: >
Basis des mitgelieferten Diffs. Fehlt, wenn stattdessen `content`
geliefert wird.
currentVersion:
type: integer
format: int64
ops:
type: array
description: Kumuliertes Diff von `fromVersion` bis `currentVersion`.
items:
$ref: "#/components/schemas/LineOperation"
content:
type: string
description: >
Volltext statt Diff - wenn `since` bereits verdichtet ist oder der
Client noch gar nichts hat (`since=0`).
events:
type: array
items:
$ref: "#/components/schemas/ChangeEvent"
ServiceInfo:
type: object
required: [name, version]
properties:
name:
type: string
version:
type: string
builtAt:
type: string
format: date-time
description: Fehlt, wenn ohne Build-Informationen gestartet (z. B. aus der IDE).
taiga:
type: boolean
description: >
true, wenn eine Taiga-Instanz konfiguriert ist
(`werkbaum.taiga.api-url`) - der Editor zeigt die Ticket-Aktionen
im Knoten-Fenster nur dann (D91).
taigaWeb:
type: string
description: >
Basis-URL des Taiga-Frontends (`werkbaum.taiga.web-url`), ohne
abschliessenden Schraegstrich - der Editor baut daraus die Links
zum Oeffnen einer Ticket-Referenz
(`<web>/project/<slug>/us/<ref>` bzw. `.../task/<ref>`).
Fehlt, wenn nicht konfiguriert; das Oeffnen entfaellt dann.
TaigaAuthRequest:
type: object
required: [username, password]
properties:
username:
type: string
minLength: 1
maxLength: 255
password:
type: string
minLength: 1
maxLength: 255
description: Wird nur durchgereicht - nie gespeichert, nie geloggt.
TaigaSession:
type: object
required: [authToken, userId, username]
properties:
authToken:
type: string
description: >
Bearer-Token der Taiga-Sitzung. Es gehoert dem Browser; der
Server merkt sich nichts davon.
userId:
type: integer
format: int64
description: Taiga-Benutzer-Id - der `member`-Filter der Projektliste.
username:
type: string
fullName:
type: string
TaigaProject:
type: object
required: [id, name, slug]
properties:
id:
type: integer
format: int64
name:
type: string
slug:
type: string
description: >
Zugleich der Wert des Schlagworts `&taiga.<slug>` im
Notationstext (SPEC par. 1, D91-Nachtrag 3).
TaigaStoryCreateRequest:
type: object
required: [project, subject]
properties:
project:
type: integer
format: int64
description: Taiga-Projekt-Id (aus der Projektliste).
subject:
type: string
minLength: 1
maxLength: 500
TaigaTaskCreateRequest:
type: object
required: [project, subject, userStory]
properties:
project:
type: integer
format: int64
subject:
type: string
minLength: 1
maxLength: 500
userStory:
type: integer
format: int64
description: Id (nicht Ref) der User Story, unter der die Task haengt.
TaigaTicket:
type: object
required: [id, ref, subject]
properties:
id:
type: integer
format: int64
ref:
type: integer
format: int64
description: >
Projektweite Nummer, fortlaufend ueber alle Typen. Werkbaum
schreibt daraus `#US-<ref>` bzw. `#T-<ref>` an die Knotenzeile -
die Praefixe traegt Werkbaum selbst, Taiga zeigt nur `#<ref>`
(D91-Nachtrag 2).
subject:
type: string
TaigaTicketDetail:
type: object
description: >
Der gelesene Stand eines Tickets (D91-Nachtrag 6) - schmal wie alles
hier: genau die Felder, die das Knoten-Fenster zeigt.
required: [id, ref, subject]
properties:
id:
type: integer
format: int64
ref:
type: integer
format: int64
subject:
type: string
status:
type: string
description: >
Name des Taiga-Workflow-Status (`status_extra_info.name`), z. B.
"In progress". Die Abbildung auf die Statusbox der Notation macht
der Editor (SPEC par. 4/9) - das Backend parst die Notation nicht
(D14). Fehlt, wenn Taiga den Namen nicht mitliefert.
statusClosed:
type: boolean
description: Taigas eigene Aussage, ob der Status als erledigt gilt.
assignee:
type: string
description: >
Anzeigename des Zustaendigen
(`assigned_to_extra_info.full_name_display`); fehlt, wenn niemand
zugewiesen ist.
version:
type: integer
format: int64
description: >
Taigas optimistische Sperre. Sie geht beim Schreiben unveraendert
zurueck (`PATCH .../status`); passt sie nicht mehr, lehnt Taiga ab
und der Konflikt wird durchgereicht.
TaigaStatus:
type: object
description: Eine Spalte des Projekt-Workflows (D91-Nachtrag 8).
required: [id, name]
properties:
id:
type: integer
format: int64
name:
type: string
description: >
Frei benannt, je Projekt verschieden - die Zuordnung zur Statusbox
der Notation macht der Editor.
closed:
type: boolean
TaigaStatusPatch:
type: object
required: [status, version]
properties:
status:
type: integer
format: int64
description: Id der Zielspalte (aus den Status-Listen), nicht ihr Name.
version:
type: integer
format: int64
description: Die zuletzt gelesene Version des Tickets.
ProblemDetail:
type: object
description: Fehlerformat nach RFC 9457 (Problem Details)
properties:
type:
type: string
title:
type: string
status:
type: integer
detail:
type: string
instance:
type: string
securitySchemes:
masterPassword:
type: http
scheme: basic
description: >
Master-Passwort fuer die Dokumentenliste. Der Hash liegt serverseitig
in einer Umgebungsvariable (`werkbaum.master-password.hash`); ist er
nicht gesetzt, bleibt die Liste gesperrt.
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: >
Noch nicht aktiv. Wird bei Einfuehrung der Autorisierung auf die
Operationen angewendet (security: - bearerAuth: []).