feat: add file serving endpoint GET /assets/:id/file
This commit is contained in:
@@ -64,7 +64,7 @@ pub async fn build_app(config: &Config) -> Result<Router> {
|
|||||||
|
|
||||||
// File storage for ingest
|
// File storage for ingest
|
||||||
let storage_path = std::env::var("STORAGE_PATH").unwrap_or_else(|_| "./data/media".to_string());
|
let storage_path = std::env::var("STORAGE_PATH").unwrap_or_else(|_| "./data/media".to_string());
|
||||||
let file_storage = Arc::new(LocalFileStorage::new(&storage_path));
|
let file_storage: Arc<LocalFileStorage> = Arc::new(LocalFileStorage::new(&storage_path));
|
||||||
|
|
||||||
// Album handlers
|
// Album handlers
|
||||||
let create_album_handler = Arc::new(CreateAlbumHandler::new(album_repo.clone()));
|
let create_album_handler = Arc::new(CreateAlbumHandler::new(album_repo.clone()));
|
||||||
@@ -78,7 +78,7 @@ pub async fn build_app(config: &Config) -> Result<Router> {
|
|||||||
quota_repo,
|
quota_repo,
|
||||||
ledger_repo,
|
ledger_repo,
|
||||||
asset_repo.clone(),
|
asset_repo.clone(),
|
||||||
file_storage,
|
file_storage.clone(),
|
||||||
event_publisher.clone(),
|
event_publisher.clone(),
|
||||||
));
|
));
|
||||||
let get_asset_handler = Arc::new(GetAssetHandler::new(
|
let get_asset_handler = Arc::new(GetAssetHandler::new(
|
||||||
@@ -90,7 +90,7 @@ pub async fn build_app(config: &Config) -> Result<Router> {
|
|||||||
metadata_repo.clone(),
|
metadata_repo.clone(),
|
||||||
));
|
));
|
||||||
let update_metadata_handler = Arc::new(UpdateMetadataHandler::new(
|
let update_metadata_handler = Arc::new(UpdateMetadataHandler::new(
|
||||||
asset_repo,
|
asset_repo.clone(),
|
||||||
metadata_repo,
|
metadata_repo,
|
||||||
event_publisher,
|
event_publisher,
|
||||||
));
|
));
|
||||||
@@ -115,6 +115,8 @@ pub async fn build_app(config: &Config) -> Result<Router> {
|
|||||||
update_metadata_handler,
|
update_metadata_handler,
|
||||||
register_volume_handler,
|
register_volume_handler,
|
||||||
register_library_path_handler,
|
register_library_path_handler,
|
||||||
|
file_storage,
|
||||||
|
asset_repo,
|
||||||
);
|
);
|
||||||
|
|
||||||
let cors = CorsLayer::new()
|
let cors = CorsLayer::new()
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ use application::{
|
|||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
Json,
|
Json,
|
||||||
|
body::Body,
|
||||||
extract::{Multipart, Path, Query, State},
|
extract::{Multipart, Path, Query, State},
|
||||||
http::StatusCode,
|
http::{StatusCode, header},
|
||||||
|
response::Response,
|
||||||
};
|
};
|
||||||
use domain::value_objects::{MetadataValue, StructuredData, SystemId};
|
use domain::value_objects::{MetadataValue, StructuredData, SystemId};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -171,3 +173,39 @@ pub async fn update_metadata(
|
|||||||
state.update_metadata_handler.execute(cmd).await?;
|
state.update_metadata_handler.execute(cmd).await?;
|
||||||
Ok(Json(serde_json::json!({ "status": "updated" })))
|
Ok(Json(serde_json::json!({ "status": "updated" })))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn serve_file(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
_claims: JwtClaims,
|
||||||
|
Path((asset_id,)): Path<(uuid::Uuid,)>,
|
||||||
|
) -> Result<Response, AppError> {
|
||||||
|
let asset = state
|
||||||
|
.asset_repo
|
||||||
|
.find_by_id(&SystemId::from_uuid(asset_id))
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| domain::errors::DomainError::NotFound("Asset not found".into()))?;
|
||||||
|
|
||||||
|
let data = state
|
||||||
|
.file_storage
|
||||||
|
.read_file(&asset.source_reference.relative_path)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.header(header::CONTENT_TYPE, &asset.mime_type)
|
||||||
|
.header(header::CONTENT_LENGTH, data.len())
|
||||||
|
.header(
|
||||||
|
header::CONTENT_DISPOSITION,
|
||||||
|
format!(
|
||||||
|
"inline; filename=\"{}\"",
|
||||||
|
asset
|
||||||
|
.source_reference
|
||||||
|
.relative_path
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or("file")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.body(Body::from(data))
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ pub fn api_v1_router() -> Router<AppState> {
|
|||||||
.route("/assets/timeline", get(assets::timeline))
|
.route("/assets/timeline", get(assets::timeline))
|
||||||
.route("/assets/{id}", get(assets::get_asset))
|
.route("/assets/{id}", get(assets::get_asset))
|
||||||
.route("/assets/{id}/metadata", put(assets::update_metadata))
|
.route("/assets/{id}/metadata", put(assets::update_metadata))
|
||||||
|
.route("/assets/{id}/file", get(assets::serve_file))
|
||||||
// storage
|
// storage
|
||||||
.route("/storage/volumes", post(storage::register_volume))
|
.route("/storage/volumes", post(storage::register_volume))
|
||||||
.route(
|
.route(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use application::{
|
|||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use domain::ports::{StoragePort, TokenIssuer};
|
use domain::ports::{AssetRepository, FileStoragePort, StoragePort, TokenIssuer};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
@@ -24,6 +24,8 @@ pub struct AppState {
|
|||||||
pub update_metadata_handler: Arc<UpdateMetadataHandler>,
|
pub update_metadata_handler: Arc<UpdateMetadataHandler>,
|
||||||
pub register_volume_handler: Arc<RegisterVolumeHandler>,
|
pub register_volume_handler: Arc<RegisterVolumeHandler>,
|
||||||
pub register_library_path_handler: Arc<RegisterLibraryPathHandler>,
|
pub register_library_path_handler: Arc<RegisterLibraryPathHandler>,
|
||||||
|
pub file_storage: Arc<dyn FileStoragePort>,
|
||||||
|
pub asset_repo: Arc<dyn AssetRepository>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@@ -43,6 +45,8 @@ impl AppState {
|
|||||||
update_metadata_handler: Arc<UpdateMetadataHandler>,
|
update_metadata_handler: Arc<UpdateMetadataHandler>,
|
||||||
register_volume_handler: Arc<RegisterVolumeHandler>,
|
register_volume_handler: Arc<RegisterVolumeHandler>,
|
||||||
register_library_path_handler: Arc<RegisterLibraryPathHandler>,
|
register_library_path_handler: Arc<RegisterLibraryPathHandler>,
|
||||||
|
file_storage: Arc<dyn FileStoragePort>,
|
||||||
|
asset_repo: Arc<dyn AssetRepository>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
register_handler,
|
register_handler,
|
||||||
@@ -59,6 +63,8 @@ impl AppState {
|
|||||||
update_metadata_handler,
|
update_metadata_handler,
|
||||||
register_volume_handler,
|
register_volume_handler,
|
||||||
register_library_path_handler,
|
register_library_path_handler,
|
||||||
|
file_storage,
|
||||||
|
asset_repo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user