4.7 Documents and Retention
KMP centralizes uploaded-file metadata and storage operations in DocumentService. Domain modules store a document reference and describe the owning entity; they do not construct filesystem/object-storage paths themselves.
Architecture
| Component | Responsibility |
|---|---|
DocumentsTable / Document |
Tenant-local metadata, ownership, soft deletion, and audit fields |
DocumentService |
Validation, persistence, download/inline responses, image thumbnails, PDF previews, reassignment, deletion |
TenantDocumentStorageConfigResolver |
Resolves tenant-aware Azure container/prefix configuration; adapter selection remains global |
ImageToPdfConversionService |
Image-to-PDF support where a domain requires it |
PdfProcessingService |
PDF validation/processing support |
RetentionPolicyService |
Validates policies and calculates/compares retention dates |
Documents use a polymorphic owner (entity_type and entity_id) plus metadata. Callers should use stable, namespaced entity types such as Members.ProfilePhoto; do not infer an owner from a filename.
Upload flow
- The controller authorizes the owning entity and action.
- The domain service validates purpose-specific file type and size constraints.
DocumentService::createDocument()stores the content through the globally configured adapter and saves metadata; the Azure adapter receives tenant-resolved container/prefix configuration.- The domain record is updated to reference the new document.
- If a replacement succeeds, the old document is removed through
deleteDocument().
If the domain save fails after upload, delete the newly created document to avoid an orphan. Existing member services demonstrate this compensating cleanup.
Serving files
Use the service’s download, inline, thumbnail, and preview responses. Every read must first authorize the owning domain record. Never expose storage paths or issue an unrestricted response based only on a document ID supplied by the client.
Legacy local paths remain in a few migration-compatible read paths. New features must use document IDs and the configured storage backend.
Retention
RetentionPolicyService supports policies anchored to gathering_end_date, upload_date, or permanent. A plugin supplies the relevant dates and persists the calculated retention date with its domain record. Deletion jobs must run inside the correct tenant context and use DocumentService so metadata and stored objects remain consistent.
Multi-tenant invariants
- Azure container/prefix configuration is resolved after tenant binding; the storage adapter comes from global application configuration.
- Object keys/containers and caches must remain tenant-isolated.
- A document entity must never cross tenant contexts.
- Platform backups and tenant document storage are separate concerns.
- Logs may include internal IDs and failure categories, but not file content, secrets, or signed storage credentials.
Verification
Test allowed and rejected MIME/extension combinations, upload rollback, authorized and denied reads, range/inline response behavior where relevant, thumbnail/preview fallbacks, retention boundaries, delete failures, and two-tenant isolation. See app/src/Services/DOCUMENT_SERVICE.md for low-level service notes maintained beside the implementation.