6e6cb0dc8d
- doc/architecture.md: arc42-Detaildokument mit Mermaid-Diagrammen (Bausteinsicht, Klassendiagramm, Statusmodell, 5 Sequenzdiagramme), Speicherkonzept, DEC-01..19-Entscheidungen mit Trade-offs - doc/requirements/: 7 Features als Gherkin-Szenarien (TDD-Grundlage) mit Traceability-Tabellen Szenario <-> Stdlib-Testname - doc/openapi.yaml: API-Vertrag fuer alle 9 Endpunkte (Quelle der Wahrheit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
289 lines
9.6 KiB
YAML
289 lines
9.6 KiB
YAML
openapi: "3.1.0"
|
|
info:
|
|
title: go-documenter API
|
|
description: |
|
|
API-first Dokumentenmanagement-System. Dieses Dokument ist die Quelle der Wahrheit
|
|
für den API-Vertrag; Endpunkte werden hier VOR der Implementierung spezifiziert.
|
|
Architektur: doc/architecture.md, Anforderungen: doc/requirements/.
|
|
version: "0.1.0"
|
|
|
|
servers:
|
|
- url: /
|
|
description: Hinter Caddy-Reverse-Proxy
|
|
|
|
security:
|
|
- bearerAuth: []
|
|
|
|
paths:
|
|
/healthz:
|
|
get:
|
|
summary: Liveness-Check mit Versionsinformation
|
|
security: []
|
|
responses:
|
|
"200":
|
|
description: Dienst läuft
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
status: { type: string, example: ok }
|
|
commit: { type: string }
|
|
tag: { type: string }
|
|
|
|
/v1/documents:
|
|
post:
|
|
summary: Dokument hochladen (PDF, JPG, PNG, HEIC)
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
required: [file]
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
responses:
|
|
"202":
|
|
description: Angenommen, Verarbeitung gestartet
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
id: { $ref: "#/components/schemas/UUID" }
|
|
status: { type: string, example: received }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"409":
|
|
description: Byte-identisches Dokument existiert bereits
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/Error"
|
|
- type: object
|
|
properties:
|
|
existing_document_id: { $ref: "#/components/schemas/UUID" }
|
|
"413": { description: Upload überschreitet das konfigurierte Größenlimit }
|
|
"422": { description: Nicht unterstützter Dateityp }
|
|
get:
|
|
summary: Dokumente auflisten (Projektion)
|
|
parameters:
|
|
- { name: status, in: query, schema: { $ref: "#/components/schemas/Status" } }
|
|
- { name: tag, in: query, schema: { type: string } }
|
|
- { name: limit, in: query, schema: { type: integer, default: 50, maximum: 200 } }
|
|
- { name: offset, in: query, schema: { type: integer, default: 0 } }
|
|
responses:
|
|
"200":
|
|
description: Liste
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
documents:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/Document" }
|
|
total: { type: integer }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
|
|
/v1/documents/{id}:
|
|
get:
|
|
summary: Einzelnes Dokument abrufen
|
|
parameters:
|
|
- { $ref: "#/components/parameters/DocumentID" }
|
|
- name: include_text
|
|
in: query
|
|
description: Volltext im Response mitliefern
|
|
schema: { type: boolean, default: false }
|
|
responses:
|
|
"200":
|
|
description: Dokument
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/Document" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/v1/documents/{id}/file:
|
|
get:
|
|
summary: Originaldatei streamen (byte-identisch zum Import)
|
|
parameters:
|
|
- { $ref: "#/components/parameters/DocumentID" }
|
|
responses:
|
|
"200":
|
|
description: Dateiinhalt
|
|
content:
|
|
application/pdf: { schema: { type: string, format: binary } }
|
|
image/jpeg: { schema: { type: string, format: binary } }
|
|
image/png: { schema: { type: string, format: binary } }
|
|
image/heic: { schema: { type: string, format: binary } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/v1/documents/{id}/events:
|
|
get:
|
|
summary: Audit-Trail — alle Events des Dokument-Aggregats
|
|
parameters:
|
|
- { $ref: "#/components/parameters/DocumentID" }
|
|
responses:
|
|
"200":
|
|
description: Eventfolge in seq-Reihenfolge
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
events:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/Event" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/v1/documents/{id}/reprocess:
|
|
post:
|
|
summary: Verarbeitung wiederaufnehmen (nach Fehler oder für Nachanreicherung)
|
|
parameters:
|
|
- { $ref: "#/components/parameters/DocumentID" }
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
from_stage:
|
|
type: string
|
|
enum: [extract, ocr, enrich, index, file]
|
|
responses:
|
|
"202": { description: Wiederaufnahme eingeplant }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
"409": { description: Dokument ist aktuell in Verarbeitung (geclaimt) }
|
|
|
|
/v1/search:
|
|
get:
|
|
summary: Volltextsuche (FTS5, BM25-Ranking)
|
|
parameters:
|
|
- { name: q, in: query, required: true, schema: { type: string } }
|
|
- { name: limit, in: query, schema: { type: integer, default: 20, maximum: 100 } }
|
|
- { name: offset, in: query, schema: { type: integer, default: 0 } }
|
|
responses:
|
|
"200":
|
|
description: Treffer nach Relevanz sortiert
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
results:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
id: { $ref: "#/components/schemas/UUID" }
|
|
title: { type: string }
|
|
original_name: { type: string }
|
|
archive_path: { type: string }
|
|
snippet: { type: string, description: Treffer-Ausschnitt mit Hervorhebung }
|
|
rank: { type: number }
|
|
total: { type: integer }
|
|
"400": { description: Fehlender oder ungültiger Suchbegriff }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
|
|
/v1/admin/search/rebuild:
|
|
post:
|
|
summary: FTS-Index vollständig aus der documents-Projektion neu aufbauen
|
|
responses:
|
|
"200":
|
|
description: Rebuild abgeschlossen
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
indexed_documents: { type: integer }
|
|
duration_ms: { type: integer }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
description: Statischer API-Token (Env `API_TOKEN`)
|
|
|
|
parameters:
|
|
DocumentID:
|
|
name: id
|
|
in: path
|
|
required: true
|
|
schema: { $ref: "#/components/schemas/UUID" }
|
|
|
|
responses:
|
|
Unauthorized:
|
|
description: Fehlender oder ungültiger Token
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/Error" }
|
|
NotFound:
|
|
description: Dokument nicht gefunden
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/Error" }
|
|
|
|
schemas:
|
|
UUID:
|
|
type: string
|
|
format: uuid
|
|
example: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
|
|
|
|
Status:
|
|
type: string
|
|
enum: [received, processing, ocr_done, enriched, indexed, filed, failed]
|
|
|
|
Error:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: object
|
|
properties:
|
|
code: { type: string, example: duplicate_document }
|
|
message: { type: string }
|
|
|
|
Event:
|
|
type: object
|
|
properties:
|
|
seq: { type: integer }
|
|
event_type: { type: string, example: DocumentReceived }
|
|
payload: { type: object, description: Event-spezifischer Payload (snake_case) }
|
|
created_at: { type: string, format: date-time }
|
|
|
|
Document:
|
|
type: object
|
|
properties:
|
|
id: { $ref: "#/components/schemas/UUID" }
|
|
status: { $ref: "#/components/schemas/Status" }
|
|
original_name: { type: string }
|
|
media_type: { type: string, enum: [pdf, jpg, png, heic] }
|
|
origin: { type: string, enum: [api, import_folder] }
|
|
content_hash: { type: string, description: SHA-256 (hex) }
|
|
size_bytes: { type: integer }
|
|
page_count: { type: integer }
|
|
title: { type: string }
|
|
summary: { type: string }
|
|
tags:
|
|
type: array
|
|
items: { type: string }
|
|
doc_date: { type: string, description: Vom LLM erkanntes Dokumentdatum (Metadatum) }
|
|
language: { type: string }
|
|
enriched: { type: boolean }
|
|
archive_path: { type: string, description: Relativ zum Archiv-Wurzelverzeichnis }
|
|
failed_stage: { type: string }
|
|
last_error: { type: string }
|
|
text: { type: string, description: Nur bei include_text=true }
|
|
imported_at: { type: string, format: date-time }
|
|
updated_at: { type: string, format: date-time }
|