diff --git a/libertas_api/src/handlers/media_handlers.rs b/libertas_api/src/handlers/media_handlers.rs index e6f6eb3..cb1819b 100644 --- a/libertas_api/src/handlers/media_handlers.rs +++ b/libertas_api/src/handlers/media_handlers.rs @@ -17,7 +17,7 @@ use crate::{ error::ApiError, extractors::query_options::ApiListMediaOptions, middleware::auth::{OptionalUserId, UserId}, - schema::{MediaDetailsResponse, MediaMetadataResponse, MediaResponse}, + schema::{MediaDetailsResponse, MediaResponse}, state::AppState, }; @@ -26,6 +26,7 @@ pub fn media_routes(max_upload_size: usize) -> Router { .route("/", post(upload_media).get(list_user_media)) .route("/{id}", get(get_media_details).delete(delete_media)) .route("/{id}/file", get(get_media_file)) + .route("/{id}/thumbnail", get(get_media_thumbnail)) .layer(DefaultBodyLimit::max(max_upload_size)) } @@ -90,26 +91,37 @@ async fn get_media_file( }) } +async fn get_media_thumbnail( + State(state): State, + OptionalUserId(user_id): OptionalUserId, + Path(media_id): Path, + request: Request, +) -> Result { + let thumbnail_path = state + .media_service + .get_media_thumbnail_path(media_id, user_id) + .await?; + + let full_path = PathBuf::from(&thumbnail_path); + + ServeFile::new(full_path) + .oneshot(request) + .await + .map_err(|e| { + ApiError::from(CoreError::Io(io::Error::new( + io::ErrorKind::NotFound, + format!("File not found: {}", e), + ))) + }) +} + async fn get_media_details( State(state): State, OptionalUserId(user_id): OptionalUserId, Path(id): Path, ) -> Result, ApiError> { let bundle = state.media_service.get_media_details(id, user_id).await?; - let response = MediaDetailsResponse { - id: bundle.media.id, - storage_path: bundle.media.storage_path, - original_filename: bundle.media.original_filename, - mime_type: bundle.media.mime_type, - hash: bundle.media.hash, - thumbnail_path: bundle.media.thumbnail_path, - metadata: bundle - .metadata - .into_iter() - .map(MediaMetadataResponse::from) - .collect(), - }; - + let response = MediaDetailsResponse::from(bundle); Ok(Json(response)) } diff --git a/libertas_api/src/schema.rs b/libertas_api/src/schema.rs index f017db6..f11471d 100644 --- a/libertas_api/src/schema.rs +++ b/libertas_api/src/schema.rs @@ -1,5 +1,6 @@ use libertas_core::models::{ - Album, AlbumPermission, FaceRegion, Media, MediaMetadata, Person, PersonPermission, Tag, + Album, AlbumPermission, FaceRegion, Media, MediaBundle, MediaMetadata, Person, + PersonPermission, Tag, }; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -7,22 +8,24 @@ use uuid::Uuid; #[derive(Serialize)] pub struct MediaResponse { pub id: uuid::Uuid, - pub storage_path: String, pub original_filename: String, pub mime_type: String, pub hash: String, - pub thumbnail_path: Option, + pub file_url: String, + pub thumbnail_url: Option, } impl From for MediaResponse { fn from(media: Media) -> Self { Self { id: media.id, - storage_path: media.storage_path, original_filename: media.original_filename, mime_type: media.mime_type, hash: media.hash, - thumbnail_path: media.thumbnail_path, + file_url: format!("/api/v1/media/{}/file", media.id), + thumbnail_url: media + .thumbnail_path + .map(|_| format!("/api/v1/media/{}/thumbnail", media.id)), } } } @@ -130,15 +133,24 @@ impl From for MediaMetadataResponse { #[derive(Serialize)] pub struct MediaDetailsResponse { - pub id: uuid::Uuid, - pub storage_path: String, - pub original_filename: String, - pub mime_type: String, - pub hash: String, - pub thumbnail_path: Option, + #[serde(flatten)] + pub media: MediaResponse, pub metadata: Vec, } +impl From for MediaDetailsResponse { + fn from(bundle: MediaBundle) -> Self { + Self { + media: MediaResponse::from(bundle.media), + metadata: bundle + .metadata + .into_iter() + .map(MediaMetadataResponse::from) + .collect(), + } + } +} + #[derive(Serialize)] pub struct TagResponse { pub id: Uuid, diff --git a/libertas_api/src/services/media_service.rs b/libertas_api/src/services/media_service.rs index 2b2cba6..d40a57b 100644 --- a/libertas_api/src/services/media_service.rs +++ b/libertas_api/src/services/media_service.rs @@ -127,6 +127,26 @@ impl MediaService for MediaServiceImpl { Ok(media.storage_path) } + async fn get_media_thumbnail_path( + &self, + id: Uuid, + user_id: Option, + ) -> CoreResult { + self.auth_service + .check_permission(user_id, authz::Permission::ViewMedia(id)) + .await?; + + let media = self + .repo + .find_by_id(id) + .await? + .ok_or(CoreError::NotFound("Media".to_string(), id))?; + + media + .thumbnail_path + .ok_or(CoreError::NotFound("Thumbnail for Media".to_string(), id)) + } + async fn delete_media(&self, id: Uuid, user_id: Uuid) -> CoreResult<()> { self.auth_service .check_permission(Some(user_id), authz::Permission::DeleteMedia(id)) diff --git a/libertas_core/src/services.rs b/libertas_core/src/services.rs index bec7ce5..0cc5006 100644 --- a/libertas_core/src/services.rs +++ b/libertas_core/src/services.rs @@ -24,6 +24,8 @@ pub trait MediaService: Send + Sync { options: ListMediaOptions, ) -> CoreResult>; async fn get_media_filepath(&self, id: Uuid, user_id: Option) -> CoreResult; + async fn get_media_thumbnail_path(&self, id: Uuid, user_id: Option) + -> CoreResult; async fn delete_media(&self, id: Uuid, user_id: Uuid) -> CoreResult<()>; }