feat: add media file retrieval functionality and update dependencies

This commit is contained in:
2025-11-02 16:22:09 +01:00
parent 4427428cf6
commit 596313b8c5
9 changed files with 205 additions and 7 deletions

128
Cargo.lock generated
View File

@@ -907,6 +907,12 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "http-range-header"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.1" version = "1.10.1"
@@ -1180,6 +1186,10 @@ dependencies = [
"sha2", "sha2",
"sqlx", "sqlx",
"tokio", "tokio",
"tower",
"tower-http",
"tracing",
"tracing-subscriber",
"uuid", "uuid",
] ]
@@ -1276,6 +1286,15 @@ version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]] [[package]]
name = "matchit" name = "matchit"
version = "0.8.4" version = "0.8.4"
@@ -1304,6 +1323,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@@ -1380,6 +1409,15 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "nuid" name = "nuid"
version = "0.5.0" version = "0.5.0"
@@ -2048,6 +2086,15 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@@ -2428,6 +2475,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.44" version = "0.3.44"
@@ -2535,9 +2591,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.16" version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -2583,6 +2639,32 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tower-http"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"http-range-header",
"httpdate",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "tower-layer" name = "tower-layer"
version = "0.3.3" version = "0.3.3"
@@ -2625,6 +2707,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@@ -2643,6 +2755,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.18" version = "0.3.18"
@@ -2706,6 +2824,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

9
compose.yml Normal file
View File

@@ -0,0 +1,9 @@
services:
nats:
image: nats:alpine
container_name: libertas_nats
ports:
- "4222:4222"
- "6222:6222"
- "8222:8222"
restart: unless-stopped

View File

@@ -32,3 +32,7 @@ sha2 = "0.10.9"
futures = "0.3.31" futures = "0.3.31"
bytes = "1.10.1" bytes = "1.10.1"
async-nats = "0.44.2" async-nats = "0.44.2"
tower = { version = "0.5.2", features = ["util"] }
tower-http = { version = "0.6.6", features = ["fs", "trace"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }

View File

@@ -46,5 +46,6 @@ pub async fn build_app_state(config: Config) -> CoreResult<AppState> {
album_service, album_service,
token_generator: tokenizer, token_generator: tokenizer,
nats_client, nats_client,
config,
}) })
} }

View File

@@ -1,14 +1,18 @@
use axum::{ use axum::{
Router, Router,
extract::{DefaultBodyLimit, Multipart, State}, extract::{DefaultBodyLimit, Multipart, Path, Request, State},
http::StatusCode, http::StatusCode,
response::Json, response::{IntoResponse, Json},
routing::post, routing::{get, post},
}; };
use futures::TryStreamExt; use futures::TryStreamExt;
use libertas_core::{error::CoreError, models::Media, schema::UploadMediaData}; use libertas_core::{error::CoreError, models::Media, schema::UploadMediaData};
use serde::Serialize; use serde::Serialize;
use std::io; use std::{io, path::PathBuf};
use tower::ServiceExt;
use tower_http::services::ServeFile;
use uuid::Uuid;
use crate::{error::ApiError, middleware::auth::UserId, state::AppState}; use crate::{error::ApiError, middleware::auth::UserId, state::AppState};
@@ -36,6 +40,7 @@ impl From<Media> for MediaResponse {
pub fn media_routes() -> Router<AppState> { pub fn media_routes() -> Router<AppState> {
Router::new() Router::new()
.route("/", post(upload_media)) .route("/", post(upload_media))
.route("/{media_id}/file", get(get_media_file))
.layer(DefaultBodyLimit::max(250 * 1024 * 1024)) .layer(DefaultBodyLimit::max(250 * 1024 * 1024))
} }
@@ -75,3 +80,27 @@ async fn upload_media(
Ok((StatusCode::CREATED, Json(media.into()))) 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),
)))
})
}

View File

@@ -1,5 +1,7 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
pub mod config; pub mod config;
pub mod error; pub mod error;
pub mod factory; pub mod factory;
@@ -13,6 +15,16 @@ pub mod state;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let config = config::load_config()?; let config = config::load_config()?;
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
format!("{}=debug,tower_http=debug", env!("CARGO_CRATE_NAME")).into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
let addr: SocketAddr = config.server_address.parse()?; let addr: SocketAddr = config.server_address.parse()?;
let app_state = factory::build_app_state(config).await?; let app_state = factory::build_app_state(config).await?;

View File

@@ -124,4 +124,18 @@ impl MediaService for MediaServiceImpl {
async fn list_user_media(&self, user_id: Uuid) -> CoreResult<Vec<Media>> { async fn list_user_media(&self, user_id: Uuid) -> CoreResult<Vec<Media>> {
self.repo.list_by_user(user_id).await self.repo.list_by_user(user_id).await
} }
async fn get_media_filepath(&self, id: Uuid, user_id: Uuid) -> CoreResult<String> {
let media = self
.repo
.find_by_id(id)
.await?
.ok_or(CoreError::NotFound("Media".to_string(), id))?;
if media.owner_id != user_id {
return Err(CoreError::Auth("Access denied".to_string()));
}
Ok(media.storage_path)
}
} }

View File

@@ -1,6 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use libertas_core::services::{AlbumService, MediaService, UserService}; use libertas_core::{
config::Config,
services::{AlbumService, MediaService, UserService},
};
use crate::security::TokenGenerator; use crate::security::TokenGenerator;
@@ -11,4 +14,5 @@ pub struct AppState {
pub album_service: Arc<dyn AlbumService>, pub album_service: Arc<dyn AlbumService>,
pub token_generator: Arc<dyn TokenGenerator>, pub token_generator: Arc<dyn TokenGenerator>,
pub nats_client: async_nats::Client, pub nats_client: async_nats::Client,
pub config: Config,
} }

View File

@@ -15,6 +15,7 @@ pub trait MediaService: Send + Sync {
async fn upload_media(&self, data: UploadMediaData<'_>) -> CoreResult<Media>; async fn upload_media(&self, data: UploadMediaData<'_>) -> CoreResult<Media>;
async fn get_media_details(&self, id: Uuid, user_id: Uuid) -> CoreResult<Media>; async fn get_media_details(&self, id: Uuid, user_id: Uuid) -> CoreResult<Media>;
async fn list_user_media(&self, user_id: Uuid) -> CoreResult<Vec<Media>>; async fn list_user_media(&self, user_id: Uuid) -> CoreResult<Vec<Media>>;
async fn get_media_filepath(&self, id: Uuid, user_id: Uuid) -> CoreResult<String>;
} }
#[async_trait] #[async_trait]