From e4b8ba550ecf22a8324624078b6a180027c9cc0a Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 3 Jun 2026 01:20:51 +0200 Subject: [PATCH] refactor: extract storage key conventions into WrapUpStorage --- crates/application/src/wrapup/delete.rs | 11 ++-- .../src/wrapup/handle_requested.rs | 34 +++-------- crates/application/src/wrapup/mod.rs | 1 + crates/application/src/wrapup/storage.rs | 60 +++++++++++++++++++ 4 files changed, 73 insertions(+), 33 deletions(-) create mode 100644 crates/application/src/wrapup/storage.rs diff --git a/crates/application/src/wrapup/delete.rs b/crates/application/src/wrapup/delete.rs index 013fd95..5f45939 100644 --- a/crates/application/src/wrapup/delete.rs +++ b/crates/application/src/wrapup/delete.rs @@ -2,18 +2,17 @@ use domain::errors::DomainError; use domain::value_objects::WrapUpId; use crate::context::AppContext; +use crate::wrapup::storage::WrapUpStorage; pub async fn execute(ctx: &AppContext, id: WrapUpId) -> Result<(), DomainError> { - let record = ctx - .repos + ctx.repos .wrapup_repo .get_by_id(&id) .await? .ok_or_else(|| DomainError::NotFound("wrap-up not found".into()))?; - let wrapup_key = format!("wrapups/{}", id.value()); - let video_key = format!("{wrapup_key}/video.mp4"); - let _ = ctx.services.image_storage.delete(&video_key).await; + let storage = WrapUpStorage::new(ctx.services.image_storage.clone()); + let _ = storage.delete_video(&id).await; - ctx.repos.wrapup_repo.delete(&record.id).await + ctx.repos.wrapup_repo.delete(&id).await } diff --git a/crates/application/src/wrapup/handle_requested.rs b/crates/application/src/wrapup/handle_requested.rs index dab6a90..bf96df4 100644 --- a/crates/application/src/wrapup/handle_requested.rs +++ b/crates/application/src/wrapup/handle_requested.rs @@ -1,5 +1,5 @@ use crate::context::AppContext; -use crate::wrapup::{compute, queries::ComputeWrapUpQuery}; +use crate::wrapup::{compute, queries::ComputeWrapUpQuery, storage::WrapUpStorage}; use domain::errors::DomainError; use domain::events::DomainEvent; use domain::models::wrapup::{DateRange, WrapUpScope, WrapUpStatus}; @@ -48,26 +48,18 @@ pub async fn execute( .await?; if let Some(ref renderer) = ctx.services.video_renderer { - let poster_images = resolve_images(ctx, &report.poster_paths, "poster").await; - let cast_keys: Vec = report - .top_cast_profile_paths - .iter() - .map(|p| format!("cast{p}")) - .collect(); - let cast_images = resolve_images(ctx, &cast_keys, "cast").await; + let asset_storage = WrapUpStorage::new(ctx.services.image_storage.clone()); + let poster_images = asset_storage.resolve_poster_images(&report.poster_paths).await; + let cast_images = asset_storage + .resolve_cast_images(&report.top_cast_profile_paths) + .await; let assets = VideoRenderAssets { poster_images, cast_images, }; match renderer.render(&report, assets).await { Ok(video_bytes) => { - let video_key = format!("wrapups/{}/video.mp4", wrapup_id.value()); - if let Err(e) = ctx - .services - .image_storage - .store(&video_key, &video_bytes) - .await - { + if let Err(e) = asset_storage.store_video(&wrapup_id, &video_bytes).await { tracing::warn!("failed to store wrapup video: {e}"); } } @@ -92,15 +84,3 @@ pub async fn execute( } } } - -async fn resolve_images(ctx: &AppContext, paths: &[String], label: &str) -> Vec<(String, Vec)> { - let mut images = Vec::new(); - for path in paths.iter().take(20) { - match ctx.services.image_storage.get(path).await { - Ok(bytes) => images.push((path.clone(), bytes)), - Err(e) => tracing::debug!("{label} fetch skipped for {path}: {e}"), - } - } - tracing::info!("resolved {}/{} {label} images", images.len(), paths.len()); - images -} diff --git a/crates/application/src/wrapup/mod.rs b/crates/application/src/wrapup/mod.rs index d2be4dd..2723685 100644 --- a/crates/application/src/wrapup/mod.rs +++ b/crates/application/src/wrapup/mod.rs @@ -7,3 +7,4 @@ pub mod get_wrapup; pub mod handle_requested; pub mod list_wrapups; pub mod queries; +pub mod storage; diff --git a/crates/application/src/wrapup/storage.rs b/crates/application/src/wrapup/storage.rs new file mode 100644 index 0000000..7230d32 --- /dev/null +++ b/crates/application/src/wrapup/storage.rs @@ -0,0 +1,60 @@ +use domain::errors::DomainError; +use domain::ports::ImageStorage; +use domain::value_objects::WrapUpId; +use std::sync::Arc; + +pub struct WrapUpStorage { + inner: Arc, +} + +impl WrapUpStorage { + pub fn new(storage: Arc) -> Self { + Self { inner: storage } + } + + pub async fn store_video(&self, id: &WrapUpId, bytes: &[u8]) -> Result<(), DomainError> { + let key = format!("wrapups/{}/video.mp4", id.value()); + self.inner.store(&key, bytes).await?; + Ok(()) + } + + pub async fn delete_video(&self, id: &WrapUpId) -> Result<(), DomainError> { + let key = format!("wrapups/{}/video.mp4", id.value()); + self.inner.delete(&key).await + } + + pub fn cast_image_key(profile_path: &str) -> String { + format!("cast{profile_path}") + } + + pub async fn resolve_cast_images( + &self, + profile_paths: &[String], + ) -> Vec<(String, Vec)> { + let mut images = Vec::new(); + for path in profile_paths.iter().take(20) { + let key = Self::cast_image_key(path); + match self.inner.get(&key).await { + Ok(bytes) => images.push((key, bytes)), + Err(e) => tracing::debug!("cast fetch skipped for {key}: {e}"), + } + } + tracing::info!("resolved {}/{} cast images", images.len(), profile_paths.len()); + images + } + + pub async fn resolve_poster_images( + &self, + paths: &[String], + ) -> Vec<(String, Vec)> { + let mut images = Vec::new(); + for path in paths.iter().take(20) { + match self.inner.get(path).await { + Ok(bytes) => images.push((path.clone(), bytes)), + Err(e) => tracing::debug!("poster fetch skipped for {path}: {e}"), + } + } + tracing::info!("resolved {}/{} poster images", images.len(), paths.len()); + images + } +}