Eviworx
Docs

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.

🔒
Funktionen
✓ Virenscan vor dem Download (ClamAV)
✓ Scan-Status (PENDING → SCANNING → CLEAN/INFECTED)
✓ Quarantäne für infizierte Dateien
✓ Gesperrte Endungen (.exe, .bat, .sh …)
✓ Prüfung des tatsächlichen Dateiinhalts
✓ Größenlimit (global 100 MB, pro Entity-Type)
✓ Anzahl-Limit pro Entity (maxFilesPerEntity)
✓ Soft-Delete bis zum Ablauf der Frist
✓ Aufbewahrungsfrist pro Entity-Type (retentionDays)
✓ Verwaiste Dateien nach 24 h entfernt

Unterstützte Entity-Types

Entity-Type Beschreibung Beispiel
TICKETScreenshots, LogsPOST /api/attachments/TICKET/:ticketId
INCIDENTPIR-Reports, ScreenshotsPOST /api/attachments/INCIDENT/:incidentId
PROBLEMRoot-Cause-AnalysenPOST /api/attachments/PROBLEM/:problemId
CHANGEImplementation-Plans, Rollback-ProceduresPOST /api/attachments/CHANGE/:changeId
ASSETPurchase-Orders, Warranty-DocsPOST /api/attachments/ASSET/:assetId
CONTRACTSigned Contracts (PDF)POST /api/attachments/CONTRACT/:contractId
LICENSELicense-CertificatesPOST /api/attachments/LICENSE/:licenseId
KB_ARTICLEScreenshots, DiagramsPOST /api/attachments/KB_ARTICLE/:articleId
WORKFLOWWorkflow-ApprovalsPOST /api/attachments/WORKFLOW/:workflowId
CUSTOM_REPORTGenerierte Reports (CSV/PDF)POST /api/attachments/CUSTOM_REPORT/:reportId
ELIBRARY_DOCUMENTeLibrary-Dokumente (Unified Attachment)POST /api/attachments/ELIBRARY_DOCUMENT/:docId
EMAIL_SIGNATUREInline-Bilder für E-Mail-SignaturenPOST /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
TICKETTicket sehen / bearbeitenMailbox + Gruppe + Vertretung + Beteiligte
INCIDENTIncident sehen / bearbeiten+ Vertretung; zugewiesene Genehmigende dürfen ebenfalls sehen
PROBLEMProblem sehen / bearbeiten+ Vertretung + zugewiesene Gruppe
CHANGEChange sehen / bearbeitenAntragsteller, Bearbeiter, Genehmigende + Vertretung
ASSETAsset sehen / ändernAsset-Typ-Sperre — Rechte pro Asset-Typ
CONTRACTVertrag sehen / bearbeiteneditAll oder editOwn als Verantwortlicher
LICENSELizenz sehen / bearbeitenBearbeiten erfordert licenses.update
KB_ARTICLEArtikel sehen / bearbeitenSichtbarkeit, Status, Freigaben; bearbeiten mit editAll oder editOwn als Autor
WORKFLOWWorkflow-Instanz sehenInitiator, Schritt-Zuweisung (inkl. Vertretung) oder workflows.viewAllInstances
CUSTOM_REPORTReport sehen und customReports.export / Ändern nur Report-Eigentümer oder deleteAllarchivierte Reports: kein Zugriff
ELIBRARY_DOCUMENTeLibrary-Sichtbarkeitarchivierte Dokumente nur mit elibrary.viewArchived (sonst 404)
EMAIL_SIGNATURESignatur-/Settings-RechtInline-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/:entityIdFile hochladen
GET/api/attachments/:entityType/:entityIdAlle Attachments einer Entity auflisten
GET/api/attachments/:idAttachment-Metadata abrufen
GET/api/attachments/:id/downloadFile herunterladen
GET/api/attachments/:id/thumbnailBild-Thumbnail (WebP) abrufen
DELETE/api/attachments/:idAttachment löschen (Soft-Delete)
GET/api/attachments/settings/:entityTypeFile-Settings für Entity-Type

File Settings (Admin)

Method Endpoint Beschreibung
GET/api/settings/file-settingsAlle Entity-Settings abrufen
GET/api/settings/file-settings/global/settingsGlobal-Settings abrufen
PUT/api/settings/file-settings/global/settingsGlobal-Settings aktualisieren
GET/api/settings/file-settings/:entityTypeEntity-Settings abrufen
PUT/api/settings/file-settings/:entityTypeEntity-Settings aktualisieren
POST/api/settings/file-settings/:entityType/resetSettings 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

  1. Nach dem Upload hat der Anhang den Scan-Status PENDING (downloadAvailable: false).
  2. ClamAV prüft die Datei; währenddessen steht der Status auf SCANNING.
  3. Ergebnis CLEAN: Die Datei ist herunterladbar, bei Bildern entsteht das Thumbnail.
  4. Ergebnis INFECTED: Die Datei wird in die Quarantäne verschoben und ist nicht herunterladbar; der hochladende Benutzer wird benachrichtigt.
  5. 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?
PENDINGWartet auf Scan (in Queue)
SCANNINGWird gerade gescannt
CLEANKein Virus gefunden
INFECTEDVirus gefunden (in Quarantine)
SCAN_ERRORScan fehlgeschlagen
SKIPPEDScan 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

errorCodeHTTPBeschreibung
NOT_FOUND404Der 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.
FORBIDDEN403Ein 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_DISABLED403Für diesen Entity-Type sind Uploads abgeschaltet — beim Hochladen wie beim Lesen der Einstellungen
NO_FILE400Kein Multipart-Feld file im Request
VALIDATION_ERROR400Schema-Verstoß mit Feld-Pfad — etwa ein Entity-Type in Kleinschreibung: der Pfad-Parameter ist strikt großgeschrieben (TICKET, nicht ticket)
FILE_TOO_LARGE413Datei größer als die wirksame Grenze (der strengere Wert aus globaler und Entity-Einstellung)
EXTENSION_BLOCKED415Endung steht in blockedExtensions
EXTENSION_NOT_ALLOWED415Endung steht nicht in allowedExtensions
MIME_TYPE_NOT_ALLOWED415MIME-Type steht nicht in allowedMimeTypes
ARCHIVE_REQUIRES_VIRUS_SCAN415Ein Archiv ohne aktiven Virenscan wird nicht angenommen
UNKNOWN_FILE_TYPE415Der Inhalt lässt sich keinem bekannten Typ zuordnen und allowUnknownMimes ist aus
BINARY_FILE_AS_TEXT415Als Text deklariert, der Inhalt ist aber binär
TEXT_TYPE_NOT_ALLOWED415Der erkannte Text-Typ ist nicht freigegeben
EXTENSION_CONTENT_MISMATCH415Die 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_EXCEEDED409Die Entität trägt bereits maxFilesPerEntity Anhänge
DUPLICATE_FILE409Am selben Vorgang liegt bereits eine Datei mit demselben INHALT (Hash, nicht Name). details.existingId nennt die vorhandene Zeile.
SCAN_PENDING423Download gesperrt: der Virenscan läuft noch
SCAN_ERROR423Download gesperrt: die Datei konnte nicht geprüft werden
INFECTED451Download gesperrt: die Datei liegt in Quarantäne
FILE_GONE410Die Zeile existiert, die Datei fehlt im Speicher
FILE_UPLOAD_RATE_LIMIT_EXCEEDED429Zu 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).

operationBeschreibung
stuck_scansEin 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_goneLebende 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.
retentionGelöschte Anhänge nach Ablauf ihrer Aufbewahrung endgültig entfernen — Zeile, Datei und Thumbnail.
orphansDateien ohne zugehörige Zeile nach der Schonfrist löschen.
infectedQuarantäne-Dateien nach ihrer eigenen Aufbewahrungsfrist entfernen.
signature_draftsNie 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.

Verwandte Dokumentation

Virus-Scan (Container)
Container Architecture — clamav + av-worker
Security — Zero-Trust Virus-Scan
Rechte & Cleanup
CronJobs — attachment_cleanup