Attachments & File Settings API
Das zentrale Anhang-System nimmt Dateien für 12 Entity-Types an — mit Virenscan (ClamAV), Datei-Einstellungen pro Entity-Type, Aufbewahrungsfristen und automatischer Bereinigung verwaister Dateien. Alle Entitäten nutzen dasselbe System — inkl. eLibrary, Custom Reports und E-Mail-Signaturen.
Unterstützte Entity-Types
| Entity-Type | Beschreibung | Beispiel |
|---|---|---|
TICKET | Screenshots, Logs | POST /api/attachments/TICKET/:ticketId |
INCIDENT | PIR-Reports, Screenshots | POST /api/attachments/INCIDENT/:incidentId |
PROBLEM | Root-Cause-Analysen | POST /api/attachments/PROBLEM/:problemId |
CHANGE | Implementation-Plans, Rollback-Procedures | POST /api/attachments/CHANGE/:changeId |
ASSET | Purchase-Orders, Warranty-Docs | POST /api/attachments/ASSET/:assetId |
CONTRACT | Signed Contracts (PDF) | POST /api/attachments/CONTRACT/:contractId |
LICENSE | License-Certificates | POST /api/attachments/LICENSE/:licenseId |
KB_ARTICLE | Screenshots, Diagrams | POST /api/attachments/KB_ARTICLE/:articleId |
WORKFLOW | Workflow-Approvals | POST /api/attachments/WORKFLOW/:workflowId |
CUSTOM_REPORT | Generierte Reports (CSV/PDF) | POST /api/attachments/CUSTOM_REPORT/:reportId |
ELIBRARY_DOCUMENT | eLibrary-Dokumente (Unified Attachment) | POST /api/attachments/ELIBRARY_DOCUMENT/:docId |
EMAIL_SIGNATURE | Inline-Bilder für E-Mail-Signaturen | POST /api/attachments/EMAIL_SIGNATURE/:signatureId |
Authentifizierung & Berechtigungen
Für Anhänge gelten die Rechte des Vorgangs, an dem sie hängen — mit genau denselben Regeln wie dort (inkl. Verantwortlichem, Vertretung, Mailbox-/Gruppen-Zuordnung, Genehmigenden, Asset-Typ-Sperre):
- Liste / Metadaten / Download: erfordert das Sichtrecht auf den Vorgang.
- Upload / Delete: erfordert das Bearbeitungsrecht am Vorgang.
- Kein Zugriff auf die Parent-Entität → immer 404, nie 403: über einen Vorgang, den der Aufrufer nicht sehen darf, verrät die API nicht einmal, dass er existiert.
- Es gibt KEIN Recht, das das Löschen oder den Download unabhängig vom Vorgang erlaubt — löschen darf, wer den Vorgang bearbeiten darf.
| Entity-Type | Sehen / Bearbeiten wie | Besonderheit |
|---|---|---|
TICKET | Ticket sehen / bearbeiten | Mailbox + Gruppe + Vertretung + Beteiligte |
INCIDENT | Incident sehen / bearbeiten | + Vertretung; zugewiesene Genehmigende dürfen ebenfalls sehen |
PROBLEM | Problem sehen / bearbeiten | + Vertretung + zugewiesene Gruppe |
CHANGE | Change sehen / bearbeiten | Antragsteller, Bearbeiter, Genehmigende + Vertretung |
ASSET | Asset sehen / ändern | Asset-Typ-Sperre — Rechte pro Asset-Typ |
CONTRACT | Vertrag sehen / bearbeiten | editAll oder editOwn als Verantwortlicher |
LICENSE | Lizenz sehen / bearbeiten | Bearbeiten erfordert licenses.update |
KB_ARTICLE | Artikel sehen / bearbeiten | Sichtbarkeit, Status, Freigaben; bearbeiten mit editAll oder editOwn als Autor |
WORKFLOW | Workflow-Instanz sehen | Initiator, Schritt-Zuweisung (inkl. Vertretung) oder workflows.viewAllInstances |
CUSTOM_REPORT | Report sehen und customReports.export / Ändern nur Report-Eigentümer oder deleteAll | archivierte Reports: kein Zugriff |
ELIBRARY_DOCUMENT | eLibrary-Sichtbarkeit | archivierte Dokumente nur mit elibrary.viewArchived (sonst 404) |
EMAIL_SIGNATURE | Signatur-/Settings-Recht | Inline-Bilder (CID) |
Sonderfall CUSTOM_REPORT: Hier reicht die Sicht auf den Report nicht aus. Über die Anhang-Endpunkte gelten dieselben Rechte wie über die Report-Endpunkte: Herunterladen verlangt zusätzlich customReports.export, Löschen/Ersetzen nur Report-Eigentümer oder deleteAll. Sonst ließe sich über /api/attachments/:id/download bzw. den Delete-Endpoint genau das umgehen, was die Report-Routen absichern — ein bloßer Betrachter eines geteilten Reports könnte fremde Export-Dateien ziehen oder löschen.
So gilt für jeden Anhang immer dasselbe Recht wie für seinen Vorgang. Details zu den Rechte-Modellen siehe Permissions & RBAC.
Endpoints Übersicht
Attachment-Operations
| Method | Endpoint | Beschreibung |
|---|---|---|
POST | /api/attachments/:entityType/:entityId | File hochladen |
GET | /api/attachments/:entityType/:entityId | Alle Attachments einer Entity auflisten |
GET | /api/attachments/:id | Attachment-Metadata abrufen |
GET | /api/attachments/:id/download | File herunterladen |
GET | /api/attachments/:id/thumbnail | Bild-Thumbnail (WebP) abrufen |
DELETE | /api/attachments/:id | Attachment löschen (Soft-Delete) |
GET | /api/attachments/settings/:entityType | File-Settings für Entity-Type |
File Settings (Admin)
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/settings/file-settings | Alle Entity-Settings abrufen |
GET | /api/settings/file-settings/global/settings | Global-Settings abrufen |
PUT | /api/settings/file-settings/global/settings | Global-Settings aktualisieren |
GET | /api/settings/file-settings/:entityType | Entity-Settings abrufen |
PUT | /api/settings/file-settings/:entityType | Entity-Settings aktualisieren |
POST | /api/settings/file-settings/:entityType/reset | Settings zurücksetzen (Defaults) |
Mount / Permissions / UI: Alle File-Settings-Routen liegen unter /api/settings/file-settings. Lesen (GET) erfordert settings.viewGeneral, Schreiben (PUT/POST reset) erfordert settings.editGeneral. In der UI: Admin-Center → System → Datei-Einstellungen (/admin/file-settings) — mit Tab „Global" (globale Defaults, /admin/file-settings?tab=global) und je einem Tab pro Entity-Typ (Ticket, Incident, Problem, Change, Asset, …). Per-Typ-Settings überschreiben die globalen Defaults (siehe Settings-Hierarchie unten).
API-Beispiele
File hochladen (zu Ticket)
POST /api/attachments/TICKET/:ticketId
Content-Type: multipart/form-data
// JavaScript
const formData = new FormData();
formData.append('file', fileBlob, 'error-screenshot.png');
const response = await fetch(`/api/attachments/TICKET/${ticketId}`, {
method: 'POST',
body: formData,
credentials: 'include'
});
const attachment = await response.json();
Response (201 Created)
{
"id": "clx...",
"entityType": "TICKET",
"entityId": "clx-ticket-123",
"originalFileName": "error-screenshot.png",
"mimeType": "image/png",
"fileSize": 125340,
"scanStatus": "PENDING",
"thumbnailPath": null,
"downloadAvailable": false,
"uploadedById": "clx-user",
"uploadedBy": { "id": "clx-user", "name": "John Doe" },
"uploadedApiKeyId": null,
"uploadedApiKey": null,
"uploadedActorName": null,
"createdAt": "2026-01-28T11:00:00Z",
"updatedAt": "2026-01-28T11:00:00Z"
}
Die Antwort ist die Zeile selbst, ohne Hülle. Der Uploader steht als Tripel: entweder uploadedById + uploadedBy (Benutzer) oder uploadedApiKeyId + uploadedApiKey (API-Key); uploadedActorName ist der Namens-Schnappschuss, wenn beides fehlt (System-Uploads oder gelöschter Urheber).
Scan-Status prüfen
GET /api/attachments/:id
Der Scan-Status steht in den Metadaten eines Anhangs und in der Liste. Die folgenden Beispiele zeigen nur die dafür relevanten Felder.
Response (Auszug, während Scan)
{
"id": "clx...",
"scanStatus": "SCANNING",
"downloadAvailable": false,
"thumbnailPath": null,
"updatedAt": "2026-01-28T11:00:05Z"
}
Response (Auszug, nach Scan - CLEAN)
{
"id": "clx...",
"scanStatus": "CLEAN",
"downloadAvailable": true,
"thumbnailPath": "thumbnails/ticket/clx-ticket-123/2026/01/2f...c9.webp",
"updatedAt": "2026-01-28T11:00:12Z"
}
Response (Auszug, INFECTED)
{
"id": "clx...",
"scanStatus": "INFECTED",
"downloadAvailable": false,
"thumbnailPath": null,
"updatedAt": "2026-01-28T11:00:15Z"
}
Hinweis: Infizierte Dateien werden in die Quarantäne verschoben und sind nicht herunterladbar. Der hochladende Benutzer wird benachrichtigt.
File herunterladen
GET /api/attachments/:id/download
Response
# Response-Headers:
Content-Type: image/png
Content-Disposition: attachment; filename="error-screenshot.png"
X-Content-Type-Options: nosniff
Content-Security-Policy: sandbox
# Response-Body: Binary File-Data
Security: Dateien sind nur herunterladbar, wenn der Scan-Status CLEAN oder SKIPPED ist. Bei PENDING/SCANNING antwortet der Download mit 423 SCAN_PENDING, bei SCAN_ERROR mit 423 SCAN_ERROR und bei INFECTED mit 451 INFECTED. Die Sperre lässt sich für niemanden aufheben.
Thumbnail abrufen (Bild-Vorschau)
GET /api/attachments/:id/thumbnail
# Response-Headers:
Content-Type: image/webp
Cache-Control: private, max-age=3600
X-Content-Type-Options: nosniff
# Response-Body: WebP-Thumbnail (max. 320px, fit inside)
Hinweis: Thumbnails werden automatisch für Raster-Bilder (JPEG/PNG/GIF/WebP — kein SVG) nach erfolgreichem Scan (CLEAN/SKIPPED) erzeugt, sofern generateThumbnails für den Entity-Typ aktiv ist. Es gelten dieselben Rechte wie beim Download (Sichtrecht auf den Vorgang), und das Thumbnail wird nur geliefert, wenn der Anhang herunterladbar ist. Kein Bild, kein Thumbnail oder kein Zugriff → 404. Nicht-Bilder behalten in der Oberfläche das generische Datei-Icon.
Alle Attachments einer Entity auflisten
GET /api/attachments/TICKET/:ticketId
Response
{
"data": [
{
"id": "clx-1",
"entityType": "TICKET",
"entityId": "clx-ticket-123",
"originalFileName": "error-screenshot.png",
"mimeType": "image/png",
"fileSize": 125340,
"scanStatus": "CLEAN",
"thumbnailPath": "thumbnails/ticket/clx-ticket-123/2026/01/2f...c9.webp",
"downloadAvailable": true,
"uploadedById": "clx-user",
"uploadedBy": { "id": "clx-user", "name": "John Doe" },
"uploadedApiKeyId": null,
"uploadedApiKey": null,
"uploadedActorName": null,
"createdAt": "2026-01-28T11:00:00Z",
"updatedAt": "2026-01-28T11:00:12Z"
},
{
"id": "clx-2",
"entityType": "TICKET",
"entityId": "clx-ticket-123",
"originalFileName": "windows-event-log.txt",
"mimeType": "text/plain",
"fileSize": 45600,
"scanStatus": "CLEAN",
"thumbnailPath": null,
"downloadAvailable": true,
"uploadedById": "clx-user",
"uploadedBy": { "id": "clx-user", "name": "John Doe" },
"uploadedApiKeyId": null,
"uploadedApiKey": null,
"uploadedActorName": null,
"createdAt": "2026-01-28T11:05:00Z",
"updatedAt": "2026-01-28T11:05:09Z"
}
]
}
Die Liste ist unpaginiert — ihre Obergrenze ist maxFilesPerEntity aus den Datei-Einstellungen. Gelöschte Anhänge sind nicht enthalten.
Attachment löschen
DELETE /api/attachments/:id
Response (204 No Content)
Automatisch:
- Der Anhang wird als gelöscht markiert (Soft-Delete) und verschwindet aus der Liste
- Die Datei bleibt für die Dauer der Aufbewahrung liegen
- Der Cleanup-Job entfernt Datei und Zeile danach endgültig
Anhänge folgen ihrem Vorgang
- In den Papierkorb: Wird ein Ticket, Incident, Problem, Change, Asset, Vertrag oder eine Lizenz gelöscht, gehen seine Anhänge mit — auch bei einer Massenlöschung.
- Und zurück: Beim Wiederherstellen kommen die Anhänge zurück, die mit dem Vorgang gefallen sind. Einzeln vorher gelöschte Anhänge bleiben gelöscht, und was die Aufbewahrung inzwischen endgültig geräumt hat, kommt nicht wieder.
- Ausnahme Custom Report: Ein Report wird hart gelöscht — seine Export-Dateien fallen deshalb sofort und endgültig mit, samt Quarantäne-Kopien und Thumbnails.
Ablauf des Virenscans
- Nach dem Upload hat der Anhang den Scan-Status PENDING (downloadAvailable: false).
- ClamAV prüft die Datei; währenddessen steht der Status auf SCANNING.
- Ergebnis CLEAN: Die Datei ist herunterladbar, bei Bildern entsteht das Thumbnail.
- Ergebnis INFECTED: Die Datei wird in die Quarantäne verschoben und ist nicht herunterladbar; der hochladende Benutzer wird benachrichtigt.
- Scheitert der Scan, steht der Status auf SCAN_ERROR; hängende Scans reiht der Cleanup-Job erneut ein (siehe unten).
Wie Scanner, Worker und Speicher voneinander abgeschottet sind, beschreiben die Seiten Security und Container Architecture.
File-Settings (Entity-Level)
Settings für TICKET abrufen
GET /api/settings/file-settings/TICKET
Response
{
"entityType": "TICKET",
"enabled": true,
"maxFileSize": 52428800,
"maxFilesPerEntity": 10,
"allowedExtensions": [".pdf", ".jpg", ".jpeg", ".png", ".gif", ".doc", ".docx", ".xls", ".xlsx", ".txt", ".csv", ".zip"],
"allowedMimeTypes": [
"application/pdf",
"image/jpeg",
"image/png",
"image/gif",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"text/plain",
"text/csv",
"application/zip"
],
"blockedExtensions": [".exe", ".bat", ".sh", ".cmd", ".msi", ".dll", ".js", ".vbs", ".ps1"],
"generateThumbnails": true,
"retentionDays": 0,
"allowUnknownMimes": false
}
Hinweis: Das Feld generateThumbnails steuert die automatische Erzeugung von Bild-Thumbnails (WebP, max. 320px) für Raster-Bilder (JPEG/PNG/GIF/WebP; kein SVG). Thumbnails entstehen nach erfolgreichem Scan (CLEAN/SKIPPED) und werden über GET /api/attachments/:id/thumbnail ausgeliefert. Pro Entity-Typ abschaltbar.
Settings aktualisieren (Admin)
PUT /api/settings/file-settings/TICKET
{
"maxFileSize": 104857600,
"maxFilesPerEntity": 20,
"retentionDays": 365,
"allowedExtensions": [".pdf", ".jpg", ".png", ".docx", ".xlsx", ".log"]
}
File-Settings (Global-Level)
Global-Settings abrufen
GET /api/settings/file-settings/global/settings
Response
{
"id": "global",
"schemaVersion": 1,
"virusScanEnabled": true,
"virusScanOnUpload": true,
"clamavRequestTimeoutMs": 30000,
"scanStuckTimeoutMinutes": 10,
"globalBlockedExtensions": [".exe", ".bat", ".sh", ".cmd", ".msi", ".dll", ".scr", ".pif", ".vbs", ".js", ".jar", ".ps1"],
"defaultStorageProvider": "DISK",
"uploadDirectory": "/app/uploads",
"orphanCleanupEnabled": true,
"orphanRetentionHours": 24,
"globalMaxFileSize": 104857600
}
Hinweis: Der Quarantäne-Pfad wird bei der Installation über die Umgebungsvariable QUARANTINE_DIR gesetzt (Default /app/quarantine, eigenes Docker-Volume, getrennt vom Upload-Volume) und ist bewusst nicht in der Oberfläche änderbar, damit er nicht versehentlich auf einen ungeeigneten Ort zeigt.
Global-Settings aktualisieren (Admin)
PUT /api/settings/file-settings/global/settings
{
"virusScanEnabled": true,
"globalMaxFileSize": 157286400,
"scanStuckTimeoutMinutes": 15,
"orphanRetentionHours": 48
}
Virus-Scan-Status
| Status | Beschreibung | Download? |
|---|---|---|
PENDING | Wartet auf Scan (in Queue) | ❌ |
SCANNING | Wird gerade gescannt | ❌ |
CLEAN | Kein Virus gefunden | ✅ |
INFECTED | Virus gefunden (in Quarantine) | ❌ |
SCAN_ERROR | Scan fehlgeschlagen | ❌ |
SKIPPED | Scan deaktiviert (Config) | ✅ |
Settings-Hierarchie
Effective-Settings-Berechnung: 1. Global-Settings (Basis): └─ globalMaxFileSize: 100MB └─ globalBlockedExtensions: [.exe, .bat, ...] └─ virusScanEnabled: true 2. Entity-Settings (Override): └─ TICKET.maxFileSize: 50MB (smaller than global) └─ TICKET.maxFilesPerEntity: 10 └─ TICKET.allowedExtensions: [.pdf, .jpg, ...] 3. Effective-Settings (Merged): └─ maxFileSize: min(global, entity) = 50MB └─ blockedExtensions: global blacklist + entity blacklist └─ allowedExtensions: entity (if set) └─ virusScanEnabled: global (cannot be disabled per entity) Beispiel: Global: 100MB TICKET: 50MB CONTRACT: 150MB → Effective: 100MB (global limit) Global blocked: [.exe, .bat] TICKET blocked: [.zip] Effective: [.exe, .bat, .zip]
Error-Handling
| errorCode | HTTP | Beschreibung |
|---|---|---|
NOT_FOUND | 404 | Der Anhang oder die Parent-Entität existiert nicht — ODER der Aufrufer darf sie nicht sehen bzw. nicht bearbeiten. Beide Fälle antworten gleich: die API verrät über einen fremden Vorgang nicht einmal, dass es ihn gibt. |
FORBIDDEN | 403 | Ein API-Key hat eine der sechs Nutzer-Routen gerufen — Hochladen, Lesen, Herunterladen und Löschen sind an einen angemeldeten Benutzer gebunden. Ausnahme: GET /settings/:entityType beantwortet auch ein API-Key. |
UPLOADS_DISABLED | 403 | Für diesen Entity-Type sind Uploads abgeschaltet — beim Hochladen wie beim Lesen der Einstellungen |
NO_FILE | 400 | Kein Multipart-Feld file im Request |
VALIDATION_ERROR | 400 | Schema-Verstoß mit Feld-Pfad — etwa ein Entity-Type in Kleinschreibung: der Pfad-Parameter ist strikt großgeschrieben (TICKET, nicht ticket) |
FILE_TOO_LARGE | 413 | Datei größer als die wirksame Grenze (der strengere Wert aus globaler und Entity-Einstellung) |
EXTENSION_BLOCKED | 415 | Endung steht in blockedExtensions |
EXTENSION_NOT_ALLOWED | 415 | Endung steht nicht in allowedExtensions |
MIME_TYPE_NOT_ALLOWED | 415 | MIME-Type steht nicht in allowedMimeTypes |
ARCHIVE_REQUIRES_VIRUS_SCAN | 415 | Ein Archiv ohne aktiven Virenscan wird nicht angenommen |
UNKNOWN_FILE_TYPE | 415 | Der Inhalt lässt sich keinem bekannten Typ zuordnen und allowUnknownMimes ist aus |
BINARY_FILE_AS_TEXT | 415 | Als Text deklariert, der Inhalt ist aber binär |
TEXT_TYPE_NOT_ALLOWED | 415 | Der erkannte Text-Typ ist nicht freigegeben |
EXTENSION_CONTENT_MISMATCH | 415 | Die Endung passt nicht zum erkannten INHALT — etwa Text als .pdf oder ein Bild als .txt. Geprüft wird gegen den tatsächlichen Inhalt, nicht gegen den vom Browser gemeldeten MIME-Type; für Endungen ohne hinterlegte Inhaltsfamilie entscheiden weiterhin allowedMimeTypes und allowUnknownMimes. |
MAX_FILES_EXCEEDED | 409 | Die Entität trägt bereits maxFilesPerEntity Anhänge |
DUPLICATE_FILE | 409 | Am selben Vorgang liegt bereits eine Datei mit demselben INHALT (Hash, nicht Name). details.existingId nennt die vorhandene Zeile. |
SCAN_PENDING | 423 | Download gesperrt: der Virenscan läuft noch |
SCAN_ERROR | 423 | Download gesperrt: die Datei konnte nicht geprüft werden |
INFECTED | 451 | Download gesperrt: die Datei liegt in Quarantäne |
FILE_GONE | 410 | Die Zeile existiert, die Datei fehlt im Speicher |
FILE_UPLOAD_RATE_LIMIT_EXCEEDED | 429 | Zu viele Uploads in kurzer Zeit |
Ein Download nicht geprüfter Dateien ist für niemanden vorgesehen — es gibt keinen Parameter und kein Recht, das die Sperre aufhebt.
Use-Cases
Use-Case 1: Ticket mit Screenshot
// 1. Create ticket
const ticket = await fetch('/api/tickets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
title: 'Error on login page',
description: 'See attached screenshot'
})
}).then(r => r.json());
// 2. Upload screenshot
const formData = new FormData();
formData.append('file', screenshotBlob, 'login-error.png');
const upload = await fetch(`/api/attachments/TICKET/${ticket.id}`, {
method: 'POST',
body: formData,
credentials: 'include'
}).then(r => r.json());
// 3. Poll scan status (every 2s)
const pollStatus = async () => {
const status = await fetch(`/api/attachments/${upload.id}`, {
credentials: 'include'
}).then(r => r.json());
if (status.scanStatus === 'CLEAN') {
console.log('File is safe, download available!');
return true;
} else if (status.scanStatus === 'INFECTED') {
alert('File is infected! Contact IT.');
return true;
}
return false; // Keep polling
};
Use-Case 2: Contract-PDFs hochladen
// Check settings (what is allowed?)
const settings = await fetch('/api/attachments/settings/CONTRACT', {
credentials: 'include'
}).then(r => r.json());
console.log('Max File Size:', settings.maxFileSize / 1024 / 1024, 'MB');
console.log('Allowed:', settings.allowedExtensions);
// Upload PDF
const formData = new FormData();
formData.append('file', pdfBlob, 'signed-contract-2026.pdf');
await fetch(`/api/attachments/CONTRACT/${contractId}`, {
method: 'POST',
body: formData,
credentials: 'include'
});
Use-Case 3: Global-Settings konfigurieren
# Admin: disable virus scan (development)
PUT /api/settings/file-settings/global/settings
{
"virusScanEnabled": false
}
# Admin: increase max file size (for large reports)
PUT /api/settings/file-settings/global/settings
{
"globalMaxFileSize": 209715200
}
# Admin: extend orphan-cleanup window
PUT /api/settings/file-settings/global/settings
{
"orphanRetentionHours": 72
}
Best Practices
💡 Tipps
1. Upload-Validierung
- • Settings VORHER laden (GET /attachments/settings/:entityType)
- • Client-Side-Validation (maxFileSize, allowedExtensions)
- • Server validiert nochmals (Defense-in-Depth)
- • Der Server prüft den tatsächlichen Dateiinhalt
2. Virus-Scan
- • Polling alle 2s für Scan-Status (nicht zu häufig)
- • Timeout nach 2min (falls Scan hängt)
- • User-Feedback bei SCANNING ("Please wait...")
- • Bei INFECTED: User-Notification + Alert an IT
3. Retention
- • Setze retentionDays per Entity-Type (Tickets: 365 Tage, Contracts: 0 = unbegrenzt)
- • CronJob: attachment_cleanup läuft täglich
- • Gelöschte Anhänge bleiben retentionDays Tage erhalten, danach entfernt der Cleanup-Job sie endgültig (0 = nie)
- • Orphan-Cleanup: Uploads ohne DB-Entry nach 24h löschen
4. Performance
- • Thumbnail-Generierung (generateThumbnails) erzeugt WebP-Vorschauen für Bild-Attachments — pro Entity-Typ abschaltbar
- • ClamAV-Timeout erhöhen bei großen Files (clamavRequestTimeoutMs)
- • Max-Files-Limit setzen (hält die Datenbank schlank)
- • Orphan-Cleanup aktiv lassen (verhindert volle Datenträger)
Integration mit Entities
Verwendung bei verschiedenen Entities: Tickets: POST /api/attachments/TICKET/:ticketId • Screenshots of error messages • Log files • User uploads (evidence) Incidents: POST /api/attachments/INCIDENT/:incidentId • Post-Incident-Review (PIR) reports • Screenshots from monitoring • Network diagrams Problems: POST /api/attachments/PROBLEM/:problemId • Root-cause-analysis reports • Vendor analysis reports • Interim-solution documentation Changes: POST /api/attachments/CHANGE/:changeId • Implementation-Plans • Rollback-Procedures • Approval-Documents Assets: POST /api/attachments/ASSET/:assetId • Purchase-Orders • Warranty-Certificates • Invoices Contracts: POST /api/attachments/CONTRACT/:contractId • Signed Contract-PDFs • Amendments • Renewal-Notices Licenses: POST /api/attachments/LICENSE/:licenseId • License-Certificates • Activation-Instructions KB-Articles: POST /api/attachments/KB_ARTICLE/:articleId • Screenshots for how-to guides • Diagrams • PDFs Workflows: POST /api/attachments/WORKFLOW/:workflowId • Approval-Documents • Supporting-Documents Custom Reports: POST /api/attachments/CUSTOM_REPORT/:reportId • Generierte CSV/PDF-Reports eLibrary: POST /api/attachments/ELIBRARY_DOCUMENT/:docId • eLibrary-Dokumente (Unified Attachment) E-Mail-Signaturen: POST /api/attachments/EMAIL_SIGNATURE/:signatureId • Inline-Bilder (CID-Referenzen)
Cleanup & Maintenance
Automatische Cleanup-Jobs
Ein Job der Action attachment_cleanup fährt sechs Operationen in fester Reihenfolge; welche laufen, bestimmt der Parameter operations (ohne Angabe: alle sechs).
| operation | Beschreibung |
|---|---|
stuck_scans | Ein Scan, der zu lange auf SCANNING steht, wird auf SCAN_ERROR gesetzt und erneut eingereiht — aber nur, wenn seine Datei noch da ist. Zeilen ohne Datei werden nicht erneut eingereiht, sondern gemeldet. |
file_gone | Lebende Zeilen, deren Datei fehlt und die älter als die Schonfrist sind, werden vom System gelöscht (Vermerk am Vorgang + Audit); der Retention-Schritt desselben Laufs räumt sie endgültig ab. |
retention | Gelöschte Anhänge nach Ablauf ihrer Aufbewahrung endgültig entfernen — Zeile, Datei und Thumbnail. |
orphans | Dateien ohne zugehörige Zeile nach der Schonfrist löschen. |
infected | Quarantäne-Dateien nach ihrer eigenen Aufbewahrungsfrist entfernen. |
signature_drafts | Nie gespeicherte Bild-Uploads aus dem Signatur-Editor aufräumen. |
Hinweis: Zugehörige Bild-Thumbnails werden mitberücksichtigt. Die Fristen selbst stehen in den Datei-Einstellungen, nicht am Job.
CronJob-Konfiguration
{
"name": "Attachment Cleanup - Daily",
"category": "MAINTENANCE",
"trigger": {
"type": "cron",
"schedule": {
"cronExpression": "0 3 * * *"
}
},
"actions": [
{
"type": "attachment_cleanup",
"parameters": {
"operations": ["stuck_scans", "file_gone", "retention", "orphans", "infected", "signature_drafts"],
"fileGoneDryRun": false
}
}
]
}
Hinweis: Das Anhang-System ist für alle zwölf Entity-Types dasselbe: eine API, die Rechte des jeweiligen Vorgangs und ein gemeinsamer Virenscan.