- 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>
5.1 KiB
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):
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)
POST /api/label-print-agent/jobs
Content-Type: application/json
Request Body
{
"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
201 Created
{ "jobId": "42" }
2. Vorschau-Bild rendern (kein Job, keine Nummer-Reservierung)
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
200 OK
Content-Type: image/png
Body: binäres PNG-Bild.
3. Nächsten Druckjob abrufen (Agent-Polling)
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
204 No Content
Antwort – Job vorhanden
200 OK
Content-Type: application/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.
GET /api/label-print-agent/jobs/{jobId}/image
Antwort
200 OK
Content-Type: image/png
Body: binäres PNG-Bild.
404 Not Found – Job oder Bild nicht vorhanden
5. Erfolgreichen Druck melden
POST /api/label-print-agent/jobs/{jobId}/printed
Content-Type: application/json
Request Body
{
"agentId": "PC-BUERO",
"printerName": "DYMO LabelWriter 450"
}
Alle Felder optional; Fallback jeweils "" / "unknown".
Antwort
200 OK
{ "ok": true }
Das Backend setzt den Job auf printed, speichert Zeitstempel und ruft die konfigurierte LabelPrintedUrl des Templates auf (POST).
6. Druckfehler melden
POST /api/label-print-agent/jobs/{jobId}/error
Content-Type: application/json
Request Body
{
"agentId": "PC-BUERO",
"printerName": "DYMO LabelWriter 450",
"errorMessage": "Drucker nicht verfügbar."
}
Antwort
200 OK
{ "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)
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 |