From f387b06e07ab99aa5ab05bc51ea5618bd9978158 Mon Sep 17 00:00:00 2001 From: crrow Date: Tue, 31 Mar 2026 19:16:56 +0800 Subject: [PATCH] fix(meta): clean up object storage on video deletion (#247) Delete all objects under raw/{video_id}/ and hls/{video_id}/ prefixes when a video is deleted. Storage cleanup errors are logged but do not block the deletion to preserve DB consistency. Closes #247 --- crates/meta/Cargo.toml | 1 + crates/meta/src/handlers.rs | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/crates/meta/Cargo.toml b/crates/meta/Cargo.toml index 70a8e0c..1a798c6 100644 --- a/crates/meta/Cargo.toml +++ b/crates/meta/Cargo.toml @@ -22,6 +22,7 @@ reqwest = { workspace = true } snafu = { workspace = true } sqlx = { workspace = true } omniqueue = { workspace = true } +futures = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/meta/src/handlers.rs b/crates/meta/src/handlers.rs index 7d96e8f..70ecb45 100644 --- a/crates/meta/src/handlers.rs +++ b/crates/meta/src/handlers.rs @@ -7,6 +7,8 @@ use axum::{ response::IntoResponse, }; use chrono::Utc; +use futures::TryStreamExt; +use object_store::ObjectStoreExt; use serde::{Deserialize, Serialize}; use snafu::ResultExt; use stream_core::{ @@ -203,6 +205,27 @@ pub async fn delete_video( // Non-fatal -- deletion proceeds even if event publishing fails. } + // Best-effort cleanup of stored objects (raw uploads + HLS segments). + // Errors are logged but do not block deletion — DB consistency is more + // important than leftover storage objects. + for prefix in ["raw", "hls"] { + let path = object_store::path::Path::from(format!("{prefix}/{video_id}/").as_str()); + let objects: Vec<_> = state + .store + .list(Some(&path)) + .try_collect() + .await + .unwrap_or_default(); + for obj in &objects { + if let Err(e) = state.store.delete(&obj.location).await { + tracing::warn!( + path = %obj.location, + "failed to delete storage object: {e}" + ); + } + } + } + // Delete processing tasks to satisfy FK constraints. task_repo::delete_for_video(&state.db, &video_id) .await