Files
paperlessmanager/docs/BACKEND_API.md
T
bjoernpoettkerandClaude Opus 5.5 3f98dfd98f fix(security): Rechteprüfung deny by default, API-Keys nur auf freigegebenen Routen
- PermissionsGuard lehnt Routen ohne @RequirePermissions/@AllowAnyAuthenticated
  ab. API-Keys umgehen keine Rechteprüfung mehr, sondern gelten nur auf
  Routen mit @AllowApiKey (Paperless-Webhook, externer Upload, checksum,
  Agent-Routen des Etikettendrucks).
- Rechte für 24 bisher offene Routen ergänzt, u.a. paperless/documents
  (nur Admin), paperless/documents/:id und Kontonummern (Posteingang/
  manuell), webhook/status (Admin). routen-rechte.spec.ts prüft alle Routen.
- Etiketten-Agent: printed/error nur für abgeholte, offene Jobs (bedingtes
  Update, sonst 409) – ein gedruckter Job kann seine Nummer nicht mehr
  freigeben. Feldwerte nur aus den Eingabefeldern der Vorlage ({number}
  nicht vorgebbar) und URL-kodiert in GET-/Callback-URLs.
- PUT paperless/inbox/:id ignoriert Freigabe (CF15) und Zahlung (CF16)
  ohne die jeweilige Rolle (Funktionstrennung).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:43 +01:00

251 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Backend API – LabelPrintAgent
Diese Datei beschreibt die Endpunkte des PaperlessManager-Backends für den LabelPrintAgent.
Das Backend rendert das fertige Etikettbild (SVG → PNG via resvg-js). Der Agent ist nur lokaler Druck-Connector.
## Authentifizierung
Alle Endpunkte erfordern einen Bearer Token (JWT oder API-Key):
```http
Authorization: Bearer {token}
```
`/events`, `/jobs/next`, `/jobs/:id/image`, `/jobs/:id/printed` und `/jobs/:id/error` sind die Agent-Routen: Sie nehmen einen **API-Key** an (`X-API-Key`, `?apiKey=` oder `Authorization: Bearer pm_…`); Benutzer mit JWT brauchen dort `MANAGE_SETTINGS`. `POST /jobs` und `POST /preview` erfordern `VIEW_SCANNER` und nehmen **keinen** API-Key an.
`printed` und `error` werden nur für vom Agenten abgeholte, noch offene Jobs angenommen, sonst `409 Conflict`. Ein wiederholtes `printed` bzw. `error` für einen bereits so abgeschlossenen Job ist ein No-op (kein zweiter Callback).
---
## 1. Job manuell anlegen (Frontend → Backend)
```http
POST /api/label-print-agent/jobs
Content-Type: application/json
```
### Request Body
```json
{
"templateId": 3,
"fieldValues": {
"datum": "2026-05-09"
}
}
```
| Feld | Pflicht | Beschreibung |
|---|---|---|
| `templateId` | ja | ID des BarcodeTemplates |
| `fieldValues` | nein | Feldwerte für Platzhalter; Datumsfelder im Format `YYYY-MM-DD` |
### Antwort
```http
201 Created
```
```json
{ "jobId": "42" }
```
---
## 2. Vorschau-Bild rendern (kein Job, keine Nummer-Reservierung)
```http
POST /api/label-print-agent/preview
Content-Type: application/json
```
### Request Body
Identisch mit `POST /jobs`. `{number}` wird immer als `1` gerendert — die GET-URL wird **nicht** aufgerufen.
### Antwort
```http
200 OK
Content-Type: image/png
```
Body: binäres PNG-Bild.
---
## 3. Nächsten Druckjob abrufen (Agent-Polling)
```http
GET /api/label-print-agent/jobs/next?agentId={agentId}
```
| Parameter | Pflicht | Beschreibung |
|---|---|---|
| `agentId` | nein | Eindeutige Agent-ID (z. B. Rechnername); Fallback: `"unknown"` |
### Antwort – kein Job vorhanden
```http
204 No Content
```
### Antwort – Job vorhanden
```http
200 OK
Content-Type: application/json
```
```json
{
"jobId": "42",
"labelImageBase64": "iVBORw0KGgoAAAANSUhEUgAA...",
"labelImageContentType": "image/png",
"labelWidthMm": 57,
"labelHeightMm": 32
}
```
| Feld | Beschreibung |
|---|---|
| `jobId` | Job-ID für Rückmeldungen |
| `labelImageBase64` | Base64-PNG; `null` wenn Rendering fehlgeschlagen |
| `labelImageContentType` | Immer `"image/png"` |
| `labelWidthMm` / `labelHeightMm` | Etikettenmaß in mm |
Das Backend setzt beim Ausliefern einen Lock (5-Minuten-TTL). Jobs mit abgelaufenem Lock werden erneut angeboten.
---
## 4. Etikettbild separat abrufen
Alternativ zum Base64-Feld in `jobs/next`.
```http
GET /api/label-print-agent/jobs/{jobId}/image
```
### Antwort
```http
200 OK
Content-Type: image/png
```
Body: binäres PNG-Bild.
```http
404 Not Found – Job oder Bild nicht vorhanden
```
---
## 5. Erfolgreichen Druck melden
```http
POST /api/label-print-agent/jobs/{jobId}/printed
Content-Type: application/json
```
### Request Body
```json
{
"agentId": "PC-BUERO",
"printerName": "DYMO LabelWriter 450"
}
```
Alle Felder optional; Fallback jeweils `""` / `"unknown"`.
### Antwort
```http
200 OK
```
```json
{ "ok": true }
```
Das Backend setzt den Job auf `printed`, speichert Zeitstempel und ruft die konfigurierte `LabelPrintedUrl` des Templates auf (`POST`).
---
## 6. Druckfehler melden
```http
POST /api/label-print-agent/jobs/{jobId}/error
Content-Type: application/json
```
### Request Body
```json
{
"agentId": "PC-BUERO",
"printerName": "DYMO LabelWriter 450",
"errorMessage": "Drucker nicht verfügbar."
}
```
### Antwort
```http
200 OK
```
```json
{ "ok": true }
```
Das Backend setzt den Job auf `error` und ruft die konfigurierte `LabelReleaseUrl` des Templates auf (`POST`).
---
## Job-Lebenszyklus
```
createJob()
│
▼
pending ──── jobs/next ──── Lock (5 Min TTL)
│
Agent druckt
│
┌───────┴───────┐
printed error
(PrintedUrl) (ReleaseUrl)
```
## 7. Server-Sent Events – neue Druckaufträge (Push)
```http
GET /api/label-print-agent/events
Authorization: Bearer {token}
Accept: text/event-stream
```
Der Agent verbindet sich einmalig. Sobald ein neuer Druckauftrag erstellt wird, sendet das Backend:
```
data: {"type":"label-job-available"}
```
Der Agent ruft daraufhin sofort `GET /jobs/next` auf. Polling bleibt als Fallback sinnvoll (z. B. alle 30 s), falls die SSE-Verbindung unterbrochen wurde.
Es werden keine agentId-Parameter ausgewertet – alle verbundenen Agents erhalten das Event.
---
## Statuscodes
| Situation | Status |
|---|---|
| Kein Job vorhanden | `204 No Content` |
| Job / Bild vorhanden | `200 OK` |
| Job erstellt | `201 Created` |
| Token fehlt / ungültig | `401 Unauthorized` |
| Fehlende Permission | `403 Forbidden` |
| Job-ID unbekannt | `404 Not Found` |
| Backend-Fehler | `500 Internal Server Error` |