refactor: update configuration handling to use environment variables and improve code organization
143 lines
4.2 KiB
Rust
143 lines
4.2 KiB
Rust
use axum::{
|
|
Router,
|
|
extract::{DefaultBodyLimit, Multipart, Path, Request, State},
|
|
http::StatusCode,
|
|
response::{IntoResponse, Json},
|
|
routing::{get, post},
|
|
};
|
|
use futures::TryStreamExt;
|
|
use libertas_core::{error::CoreError, models::Media, schema::UploadMediaData};
|
|
use std::{io, path::PathBuf};
|
|
|
|
use tower::ServiceExt;
|
|
use tower_http::services::ServeFile;
|
|
use uuid::Uuid;
|
|
|
|
use crate::{error::ApiError, extractors::query_options::ApiListMediaOptions, middleware::auth::UserId, schema::{MediaDetailsResponse, MediaMetadataResponse, MediaResponse}, state::AppState};
|
|
|
|
|
|
impl From<Media> 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,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn media_routes(max_upload_size: usize) -> Router<AppState> {
|
|
Router::new()
|
|
.route("/", post(upload_media).get(list_user_media))
|
|
.route("/{id}", get(get_media_details).delete(delete_media))
|
|
.route("/{id}/file", get(get_media_file))
|
|
.layer(DefaultBodyLimit::max(max_upload_size))
|
|
}
|
|
|
|
async fn upload_media(
|
|
State(state): State<AppState>,
|
|
UserId(user_id): UserId,
|
|
mut multipart: Multipart,
|
|
) -> Result<(StatusCode, Json<MediaResponse>), ApiError> {
|
|
let field = multipart
|
|
.next_field()
|
|
.await
|
|
.map_err(|e| CoreError::Validation(format!("Multipart error: {}", e)))?
|
|
.ok_or(ApiError::from(CoreError::Validation(
|
|
"No file provided in 'file' field".to_string(),
|
|
)))?;
|
|
|
|
let filename = field.file_name().unwrap_or("unknown_file").to_string();
|
|
let mime_type = field
|
|
.content_type()
|
|
.unwrap_or("application/octet-stream")
|
|
.to_string();
|
|
|
|
let stream = field.map_err(|e| io::Error::new(io::ErrorKind::Other, e));
|
|
|
|
let boxed_stream: Box<
|
|
dyn futures::Stream<Item = Result<bytes::Bytes, std::io::Error>> + Send + Unpin,
|
|
> = Box::new(stream);
|
|
|
|
let upload_data = UploadMediaData {
|
|
owner_id: user_id,
|
|
filename,
|
|
mime_type,
|
|
stream: boxed_stream,
|
|
};
|
|
|
|
let media = state.media_service.upload_media(upload_data).await?;
|
|
|
|
Ok((StatusCode::CREATED, Json(media.into())))
|
|
}
|
|
|
|
async fn get_media_file(
|
|
State(state): State<AppState>,
|
|
UserId(user_id): UserId,
|
|
Path(media_id): Path<Uuid>,
|
|
request: Request,
|
|
) -> Result<impl IntoResponse, ApiError> {
|
|
let storage_path = state
|
|
.media_service
|
|
.get_media_filepath(media_id, user_id)
|
|
.await?;
|
|
|
|
let full_path = PathBuf::from(&state.config.media_library_path).join(&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),
|
|
)))
|
|
})
|
|
}
|
|
|
|
async fn get_media_details(
|
|
State(state): State<AppState>,
|
|
UserId(user_id): UserId,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<MediaDetailsResponse>, 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,
|
|
metadata: bundle.metadata
|
|
.into_iter()
|
|
.map(MediaMetadataResponse::from)
|
|
.collect(),
|
|
};
|
|
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn delete_media(
|
|
State(state): State<AppState>,
|
|
UserId(user_id): UserId,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<StatusCode, ApiError> {
|
|
state.media_service.delete_media(id, user_id).await?;
|
|
Ok(StatusCode::NO_CONTENT)
|
|
}
|
|
|
|
async fn list_user_media(
|
|
State(state): State<AppState>,
|
|
UserId(user_id): UserId,
|
|
ApiListMediaOptions(options): ApiListMediaOptions,
|
|
) -> Result<Json<Vec<MediaResponse>>, ApiError> {
|
|
let media_list = state
|
|
.media_service
|
|
.list_user_media(user_id, options)
|
|
.await?;
|
|
|
|
let response = media_list.into_iter().map(MediaResponse::from).collect();
|
|
Ok(Json(response))
|
|
} |