Files
Christoph Kroczek 6e6cb0dc8d M0: arc42-Konzeptdokument, Gherkin-Anforderungen und OpenAPI-Vertrag
- 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>
2026-07-13 15:20:48 +02:00

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 }