diff --git a/Cargo.lock b/Cargo.lock index 857869b..fca6522 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,6 +632,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "testdir", "time 0.3.27", "tracing", "uuid", @@ -1798,6 +1799,8 @@ dependencies = [ "serde", "serde_json", "sqlx", + "testdir", + "time 0.3.27", "tokio", "tower", "tower-http", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index e6d4ef9..2316765 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -27,3 +27,6 @@ serde_with.workspace = true time.workspace = true tracing.workspace = true uuid = { workspace = true, features = ["serde"] } + +[dev-dependencies] +testdir.workspace = true diff --git a/crates/common/src/config/configuration.rs b/crates/common/src/config/configuration.rs index 70c288c..2008fe5 100644 --- a/crates/common/src/config/configuration.rs +++ b/crates/common/src/config/configuration.rs @@ -16,7 +16,7 @@ */ //! This defines the app configuration -use std::{fmt, fs}; +use std::{fmt, fs, path::PathBuf}; use serde::Deserialize; use tracing::info; @@ -43,6 +43,7 @@ impl Configuration { Some(config) } + /// Use this for tests pub fn empty() -> Self { Configuration { internal_url: "".into(), diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml index 458fbf7..4f5f41c 100644 --- a/crates/media/Cargo.toml +++ b/crates/media/Cargo.toml @@ -17,7 +17,10 @@ doctest = false [dependencies] common.workspace = true -database = { path = "../database" } +database.workspace = true +# database = { path = "../database" } + +time.workspace = true tracing.workspace = true # serialization @@ -34,9 +37,12 @@ uuid = { workspace = true, features = ["serde"] } sqlx.workspace = true rand.workspace = true + +[dev-dependencies] # testing mockall.workspace = true rstest.workspace = true tokio.workspace = true tower = { workspace = true, features = ["util"] } hyper = { workspace = true, features = ["full"] } +testdir.workspace = true diff --git a/crates/media/src/api/router.rs b/crates/media/src/api/router.rs index 584ac8e..d4cf2ed 100644 --- a/crates/media/src/api/router.rs +++ b/crates/media/src/api/router.rs @@ -42,7 +42,7 @@ impl MediaApi { S: Send + Sync + Clone, { let media_repository: MediaRepository = - MediaRepository::new(state.database.clone()).await; + MediaRepository::new(state.database.clone(), state.config.clone()).await; let repository_state: MediaRepositoryState = Arc::new(media_repository); Router::new() diff --git a/crates/media/src/api/routes/post_media.rs b/crates/media/src/api/routes/post_media.rs index e7788e4..e1adc30 100644 --- a/crates/media/src/api/routes/post_media.rs +++ b/crates/media/src/api/routes/post_media.rs @@ -1,19 +1,70 @@ //! Creates a new media item to aggregate related files for current user //! -use axum::{extract::Multipart, http::StatusCode}; +use axum::{ + extract::{Multipart, State}, + http::StatusCode, +}; use common::auth::user::User; use tracing::{debug, error}; +use uuid::Uuid; + +use crate::{data::error::DataAccessError, repository::MediaRepositoryState}; pub(crate) async fn post_media( + State(repo): State, user: User, - mut _multipart: Multipart, + mut multipart: Multipart, ) -> std::result::Result { - error!("POST /media user={}", user); + let mut name = None; + let mut date_taken = None; + + while let Some(field) = multipart.next_field().await.unwrap() { + if let Some(field_name) = field.name() { + match field_name { + "name" => { + name = Some(field.text().await.unwrap()); + // debug!("name={}", field.text().await.unwrap()); + } + "date_taken" => { + date_taken = Some(field.text().await.unwrap()); + // debug!("date_taken={}", field.text().await.unwrap()); + } + _ => continue, + } + } + } + + if name.is_none() || date_taken.is_none() { + return Err(StatusCode::BAD_REQUEST); + } + + let result = repo.create_media_item_for_user( + Uuid::parse_str(user.uuid.as_str()).unwrap(), + name.clone().unwrap(), + date_taken.clone().unwrap(), + ); - let id = uuid::Uuid::new_v4(); - debug!("add media with id {} into database", id); - // TODO: check if media already exists for user + match result { + Ok(uuid) => { + debug!( + "name={}, taken={} => id={}", + name.unwrap(), + date_taken.unwrap(), + uuid.clone().hyphenated().to_string() + ); - // TODO: add item in database for user - Err(StatusCode::NOT_IMPLEMENTED) + Ok(uuid.hyphenated().to_string()) + } + Err(error) => { + match error { + DataAccessError::AlreadyExist => { + // TODO: use Redirect::permanent to add a Location header to the already existing item + return Err(StatusCode::SEE_OTHER); + } + _ => { + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + } + } + } } diff --git a/crates/media/src/api/routes/post_media_id.rs b/crates/media/src/api/routes/post_media_id.rs index f9b34bc..fa66d6b 100644 --- a/crates/media/src/api/routes/post_media_id.rs +++ b/crates/media/src/api/routes/post_media_id.rs @@ -15,8 +15,8 @@ pub(crate) async fn post_media_id( while let Some(mut field) = multipart.next_field().await.unwrap() { if let Some(field_name) = field.name() { match field_name { - "description" => { - debug!("description={}", field.text().await.unwrap()); + "name" => { + debug!("name={}", field.text().await.unwrap()); } "file" => { // TODO: wrap bytes and write to persistency diff --git a/crates/media/src/data/error.rs b/crates/media/src/data/error.rs index 699a12a..cae01bd 100644 --- a/crates/media/src/data/error.rs +++ b/crates/media/src/data/error.rs @@ -19,6 +19,8 @@ pub enum DataAccessError { NotFound, #[allow(dead_code)] + InvalidDateFormat, + AlreadyExist, TechnicalError, #[allow(dead_code)] OtherError, diff --git a/crates/media/src/repository.rs b/crates/media/src/repository.rs index e831542..cdeef35 100644 --- a/crates/media/src/repository.rs +++ b/crates/media/src/repository.rs @@ -16,8 +16,10 @@ */ use axum::async_trait; +use common::config::configuration::Configuration; use std::sync::Arc; use std::time::Instant; +use time::OffsetDateTime; use tracing::info; use uuid::Uuid; @@ -27,6 +29,7 @@ use crate::data::media_item::MediaItem; #[allow(dead_code)] pub struct MediaRepository { pub(crate) database: D, + pub(crate) config: Configuration, } pub type MediaRepositoryState = Arc; @@ -37,11 +40,19 @@ pub type MediaRepositoryState = Arc; pub trait MediaRepositoryTrait { // Gets a list of media items from the DB filted by user_id fn get_media_items_for_user(&self, user_id: Uuid) -> Result, DataAccessError>; + + /// Create a new media item for the given user + fn create_media_item_for_user( + &self, + user_id: Uuid, + name: String, + date_taken: String, + ) -> Result; } impl MediaRepository { - pub async fn new(database: D) -> Self { - Self { database } + pub async fn new(database: D, config: Configuration) -> Self { + Self { database, config } } } @@ -63,6 +74,17 @@ impl MediaRepositoryTrait for MediaRepository { references: None, }]) } + + /// inside impl + fn create_media_item_for_user( + &self, + user_id: Uuid, + name: String, + date_taken: String, + ) -> Result { + // Err(DataAccessError::AlreadyExist) + Ok(Uuid::new_v4()) + } } #[allow(unused_imports)] @@ -91,7 +113,7 @@ mod tests { let db = SqliteDatabase::new("target/sqlx/test-dbs/media/repository/tests/test_new.sqlite") .await; - let repository = MediaRepository::new(db).await; + let repository = MediaRepository::new(db, Configuration::empty()).await; // when let result = repository.get_media_items_for_user(Uuid::new_v4());