fix: prevent blob storage leak on folder deletion
- Add PG trigger trg_files_decrement_blob_ref (AFTER DELETE ON storage.files) that auto-decrements storage.blobs.ref_count for every deleted file row. Covers all paths: explicit DELETE, ON DELETE CASCADE, trash emptying. - Remove manual remove_reference() call from delete_file() in file_blob_write_repository — trigger is now the single source of truth. - Fix double-decrement bug in FileManagementService::delete_with_cleanup: was decrementing ref_count on trash (soft-delete) when the file row still existed, causing premature blob GC and potential data corruption on restore. - Remove dead fields (file_read, dedup_service) from FileManagementService and simplify constructors — ref_count fully handled by PG trigger.
This commit is contained in:
@@ -449,19 +449,18 @@ impl FileWritePort for FileBlobWriteRepository {
|
||||
}
|
||||
|
||||
async fn delete_file(&self, id: &str) -> Result<(), DomainError> {
|
||||
// Atomic DELETE RETURNING — one round-trip instead of SELECT + DELETE
|
||||
let hash = sqlx::query_scalar::<_, String>(
|
||||
"DELETE FROM storage.files WHERE id = $1::uuid RETURNING blob_hash",
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(self.pool.as_ref())
|
||||
.await
|
||||
.map_err(|e| DomainError::internal_error("FileBlobWrite", format!("delete: {e}")))?
|
||||
.ok_or_else(|| DomainError::not_found("File", id))?;
|
||||
// The PG trigger `trg_files_decrement_blob_ref` automatically
|
||||
// decrements storage.blobs.ref_count for the deleted row's blob_hash.
|
||||
// Disk cleanup of orphaned blobs (ref_count = 0) is handled by
|
||||
// garbage_collect().
|
||||
let result = sqlx::query("DELETE FROM storage.files WHERE id = $1::uuid")
|
||||
.bind(id)
|
||||
.execute(self.pool.as_ref())
|
||||
.await
|
||||
.map_err(|e| DomainError::internal_error("FileBlobWrite", format!("delete: {e}")))?;
|
||||
|
||||
// Decrement blob reference (best-effort after successful DELETE)
|
||||
if let Err(e) = self.dedup.remove_reference(&hash).await {
|
||||
tracing::warn!("Failed to decrement blob ref for {}: {}", &hash[..12], e);
|
||||
if result.rows_affected() == 0 {
|
||||
return Err(DomainError::not_found("File", id));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user