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 /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 /projects?member=` - die Auswahlliste des Projekt-Dialogs der Ticket-Anlage. Der `slug` ist zugleich der Wert des Schlagworts `&taiga.` (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 /userstories`. Die Antwort traegt die projektweite `ref` - Werkbaum schreibt daraus `#US-` 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 /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-`. 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-` 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.` des Teilbaums nimmt (SPEC par. 1). Der Proxy fragt dafuer zuerst `GET /projects/by_slug`, dann `GET /userstories/by_ref?project=&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 /tasks/by_ref` - loest `#T-` 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 /userstory-statuses?project=`. 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 `-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.` (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:` 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 (`/project//us/` bzw. `.../task/`). 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.` 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-` bzw. `#T-` an die Knotenzeile - die Praefixe traegt Werkbaum selbst, Taiga zeigt nur `#` (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: []).