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