fix: OOM protection, lock-free thumbnail cache, OIDC JWKS TTL

- Streaming WebDAV PUT: body spooled to tempfile with incremental SHA-256,
  peak RAM ~64KB regardless of file size (Solution 2)
- RequestBodyLimitLayer (1MB) on CalDAV/CardDAV routers (Solution 3)
- All body::to_bytes(body, usize::MAX) replaced with explicit limits:
  PROPFIND/PROPPATCH/LOCK → 1MB, MKCOL → 4KB
- Added AppError::payload_too_large (HTTP 413)
- Added max_upload_size to StorageConfig (default 10GB, env override)
- New streaming update chain: FileWritePort::update_file_content_from_temp
  → FileUploadUseCase::update_file_streaming
- ThumbnailService: migrated from RwLock<LruCache> to moka::future::Cache
  with weight-based eviction — eliminates lock contention on read hot-path
- OIDC: discovery + JWKS caches now expire after 1 hour (Cached<T> wrapper)
  so IdP key rotation no longer requires server restart
This commit is contained in:
Diocrafts
2026-02-22 23:28:03 +01:00
parent b48f2867ac
commit 92e0364a60
12 changed files with 395 additions and 172 deletions
+14
View File
@@ -70,6 +70,20 @@ pub trait FileUploadUseCase: Send + Sync + 'static {
/// Updates the content of an existing file (for WebDAV)
async fn update_file(&self, path: &str, content: &[u8]) -> Result<(), DomainError>;
/// Streaming update — spools body to a temp file with incremental hash,
/// then atomically replaces the file content via dedup store.
///
/// Peak RAM: ~256 KB regardless of file size.
/// Used by WebDAV PUT for large files.
async fn update_file_streaming(
&self,
path: &str,
temp_path: &Path,
size: u64,
content_type: &str,
pre_computed_hash: Option<String>,
) -> Result<(), DomainError>;
}
// ─────────────────────────────────────────────────────
+14
View File
@@ -164,6 +164,20 @@ pub trait FileWritePort: Send + Sync + 'static {
async fn update_file_content(&self, file_id: &str, content: Vec<u8>)
-> Result<(), DomainError>;
/// Streaming update — replaces file content from a temp file on disk.
///
/// When `pre_computed_hash` is provided, the dedup service skips the
/// hash re-read — zero extra I/O beyond the initial spool.
/// Peak RAM: ~256 KB regardless of file size.
async fn update_file_content_from_temp(
&self,
file_id: &str,
temp_path: &std::path::Path,
size: u64,
content_type: Option<String>,
pre_computed_hash: Option<String>,
) -> Result<(), DomainError>;
/// Registers file metadata WITHOUT writing content to disk (write-behind).
///
/// Returns `(File, PathBuf)` where `PathBuf` is the destination path for the