feat: Add media serving functionality with optional metadata stripping

This commit is contained in:
2025-11-15 18:36:34 +01:00
parent ccb9f09d4a
commit e6c941bf28
7 changed files with 108 additions and 16 deletions

View File

@@ -1,13 +1,19 @@
use axum::{
Router,
extract::{DefaultBodyLimit, Multipart, Path, Request, State},
http::StatusCode,
extract::{DefaultBodyLimit, Multipart, Path, Query, Request, State},
http::{
StatusCode,
header::{CONTENT_DISPOSITION, CONTENT_TYPE},
},
response::{IntoResponse, Json},
routing::{get, post},
};
use futures::TryStreamExt;
use libertas_core::{error::CoreError, schema::UploadMediaData};
use libertas_core::{
error::CoreError, media_utils::strip_metadata_from_bytes, schema::UploadMediaData,
};
use std::{io, path::PathBuf};
use tokio::fs;
use tower::ServiceExt;
use tower_http::services::ServeFile;
@@ -17,7 +23,10 @@ use crate::{
error::ApiError,
extractors::query_options::ApiListMediaOptions,
middleware::auth::{OptionalUserId, UserId},
schema::{MediaDetailsResponse, MediaResponse, PaginatedResponse, map_paginated_response},
schema::{
MediaDetailsResponse, MediaResponse, PaginatedResponse, ServeFileQuery,
map_paginated_response,
},
state::AppState,
};
@@ -71,24 +80,49 @@ async fn get_media_file(
State(state): State<AppState>,
OptionalUserId(user_id): OptionalUserId,
Path(media_id): Path<Uuid>,
Query(query): Query<ServeFileQuery>,
request: Request,
) -> Result<impl IntoResponse, ApiError> {
let storage_path = state
let media = state
.media_service
.get_media_filepath(media_id, user_id)
.get_media_for_serving(media_id, user_id)
.await?;
let full_path = PathBuf::from(&state.config.media_library_path).join(&storage_path);
let full_path = PathBuf::from(&state.config.media_library_path).join(&media.storage_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),
)))
})
match query.strip {
true => {
let file_bytes = fs::read(&full_path).await.map_err(|e| {
ApiError::from(CoreError::Io(io::Error::new(
io::ErrorKind::NotFound,
format!("File not found at {}: {}", full_path.display(), e),
)))
})?;
let body = strip_metadata_from_bytes(file_bytes, &media.mime_type).await?;
let disposition = format!("inline; filename=\"{}\"", media.original_filename);
let response = (
StatusCode::OK,
[
(CONTENT_TYPE, media.mime_type),
(CONTENT_DISPOSITION, disposition),
],
body,
)
.into_response();
Ok(response)
}
false => 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),
)))
})
.map(|res| res.into_response()),
}
}
async fn get_media_thumbnail(