diff --git a/Cargo.lock b/Cargo.lock index 5d50637..645ad80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -485,7 +485,7 @@ checksum = "221eaa237ddf1ca79b60d1372aad77e47f9c0ea5b3ce5099da8c61d027dc77b3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -981,7 +981,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1018,7 +1018,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1053,7 +1053,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1341,7 +1341,7 @@ checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2022,7 +2022,7 @@ checksum = "458ace39169e4b83c4f77ae3d42d5d1d11c422feef590219a97c973d3b524557" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2240,7 +2240,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2613,7 +2613,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 2.0.119", "walkdir", ] @@ -2809,9 +2809,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2819,29 +2819,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "indexmap", "itoa", @@ -2981,8 +2981,8 @@ dependencies = [ [[package]] name = "simodels" -version = "0.11.3" -source = "git+ssh://git@git.kundeng.us/phoenix/simodels.git?tag=v0.11.3-main-fe9d101bd0-111#fe9d101bd019f91fe8c99c29721ccb0e6e90c92c" +version = "0.11.4" +source = "git+ssh://git@git.kundeng.us/phoenix/simodels.git?tag=v0.11.4#d6d719741945ef7719ab1db3e0ef3c617b5bb909" dependencies = [ "josekit", "rand 0.10.2", @@ -3022,7 +3022,7 @@ dependencies = [ [[package]] name = "soaricarus_api" -version = "0.5.5" +version = "0.5.6" dependencies = [ "axum", "axum-extra", @@ -3157,7 +3157,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.119", ] [[package]] @@ -3180,7 +3180,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.119", "thiserror 2.0.18", "tokio", "url", @@ -3311,6 +3311,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3325,7 +3336,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3367,7 +3378,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3378,7 +3389,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3392,9 +3403,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.53" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "num-conv", @@ -3412,9 +3423,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -3470,7 +3481,7 @@ checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3586,7 +3597,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3726,7 +3737,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn", + "syn 2.0.119", "uuid", ] @@ -3841,7 +3852,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -3988,7 +3999,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -4009,7 +4020,7 @@ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4029,7 +4040,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -4050,7 +4061,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4083,7 +4094,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7038430..f9ac9d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soaricarus_api" -version = "0.5.5" +version = "0.5.6" edition = "2024" rust-version = "1.95" license = "MIT" @@ -10,8 +10,8 @@ description = "Core API for interacting with soaricarus" axum = { version = "0.8.9", features = ["multipart"] } axum-extra = { version = "0.12.6", features = ["cookie"] } openssl = { version = "0.10.81", features = ["vendored"] } -serde = { version = "1.0.228", features = ["derive"] } -serde_json = { version = "1.0.150" } +serde = { version = "1.0.229", features = ["derive"] } +serde_json = { version = "1.0.151" } tokio = { version = "1.52.3", features = ["full"] } tokio-util = { version = "0.7.18", features = ["io"] } tower = { version = "0.5.3", features = ["full"] } @@ -19,15 +19,15 @@ tower-http = { version = "0.7.0", features = ["cors", "timeout"] } tracing-subscriber = "0.3.23" futures = { version = "0.3.32" } mime_guess = { version = "2.0.5" } -uuid = { version = "1.23.5", features = ["v4", "serde"] } +uuid = { version = "1.24.0", features = ["v4", "serde"] } sqlx = { version = "0.9.0", features = ["postgres", "runtime-tokio", "tls-native-tls", "time", "uuid"] } -time = { version = "0.3.53", features = ["formatting", "macros", "parsing", "serde"] } +time = { version = "0.3.54", features = ["formatting", "macros", "parsing", "serde"] } jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } simeta = { git = "ssh://git@git.kundeng.us/phoenix/simeta.git", tag = "v0.6.1-main-b690995806-680" } -simodels = { git = "ssh://git@git.kundeng.us/phoenix/simodels.git", tag = "v0.11.3-main-fe9d101bd0-111" } +simodels = { git = "ssh://git@git.kundeng.us/phoenix/simodels.git", tag = "v0.11.4" } sienvy = { git = "ssh://git@git.kundeng.us/phoenix/sienvy.git", tag = "v0.8.0-main-d06c8fdf49-006" } labyrinth = { git = "ssh://git@git.kundeng.us/phoenix/labyrinth.git", tag = "v0.0.4" } diff --git a/migrations/20250420185217_init_migration.sql b/migrations/20250420185217_init_migration.sql index 4cee36b..100c473 100644 --- a/migrations/20250420185217_init_migration.sql +++ b/migrations/20250420185217_init_migration.sql @@ -63,6 +63,7 @@ CREATE TABLE IF NOT EXISTS "song" ( date_created timestamptz DEFAULT now(), filename TEXT NOT NULL, directory TEXT NOT NULL, + file_key TEXT NOT NULL, user_id UUID NULL -- TODO: Add coverart id later. This will allow multiple songs to be linked to a single cover art ); diff --git a/src/callers/queue/coverart.rs b/src/callers/queue/coverart.rs index 0025b14..956fb9f 100644 --- a/src/callers/queue/coverart.rs +++ b/src/callers/queue/coverart.rs @@ -182,7 +182,7 @@ pub mod endpoint { let lr = labyrinth::Labyrinth { config: lab_config }; let data = labyrinth::Data { - raw_data: raw_data, + raw_data, ..Default::default() }; diff --git a/src/callers/song.rs b/src/callers/song.rs index 656ae47..53b4268 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -159,8 +159,14 @@ pub mod endpoint { } } - match song.save_to_filesystem() { - Ok(_) => match repo::song::insert(&pool, &song).await { + song.file_key = format!("processed/song/{}", song.filename); + let data_of_song = labyrinth::Data { + raw_data: song.data.clone(), + ..Default::default() + }; + + match lr.upload(&song.file_key, &data_of_song).await { + Ok(_resp) => match repo::song::insert(&pool, &song).await { Ok((date_created, id)) => { song.id = id; song.date_created = Some(date_created); @@ -176,7 +182,7 @@ pub mod endpoint { } }, Err(err) => { - response.message = err.to_string(); + eprintln!("Error: {err:?}"); ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response), @@ -290,40 +296,90 @@ pub mod endpoint { axum::extract::Path(id): axum::extract::Path, ) -> impl IntoResponse { match repo::song::get_song(&pool, &id).await { - Ok(song) => { - let song_path = song.song_path().unwrap(); - let path = std::path::Path::new(&song_path); + Ok(mut song) => { + let lab_config = crate::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; - if !path.starts_with(&song.directory) || !path.exists() { - return Err((axum::http::StatusCode::NOT_FOUND, "File not found")); - } + match lr.download(&song.file_key).await { + Ok(data) => { + song.filename = simodels::song::generate_filename( + simodels::types::MusicType::FlacExtension, + true, + ) + .unwrap(); + // TODO: At some point, directory will no longer be needed with s3 + song.directory = sienvy::environment::get_root_directory().value; + song.data = data; - let file = match tokio::fs::File::open(&path).await { - Ok(file) => file, - Err(_) => return Err((axum::http::StatusCode::NOT_FOUND, "File not found")), - }; + match song.save_to_filesystem() { + Ok(_) => { + let song_path = song.song_path().unwrap(); + let path = std::path::Path::new(&song_path); - let file_size = match file.metadata().await { - Ok(meta) => meta.len(), - Err(_) => { - return Err(( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - "Could not read file", - )); + if !path.starts_with(&song.directory) || !path.exists() { + return Err(( + axum::http::StatusCode::NOT_FOUND, + "File not found", + )); + } + + let file = match tokio::fs::File::open(&path).await { + Ok(file) => file, + Err(_) => { + return Err(( + axum::http::StatusCode::NOT_FOUND, + "File not found", + )); + } + }; + + let file_size = match file.metadata().await { + Ok(meta) => meta.len(), + Err(_) => { + return Err(( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "Could not read file", + )); + } + }; + + let mime = mime_guess::from_path(path).first_or_octet_stream(); + let stream = tokio_util::io::ReaderStream::new(file); + + let rep = axum::response::Response::builder() + .header("content-type", mime.to_string()) + .header("accept-ranges", "bytes") + .header("content-length", file_size.to_string()) + .body(axum::body::Body::from_stream(stream)) + .unwrap(); + match song.remove_from_filesystem() { + Ok(_) => Ok(rep), + Err(err) => { + eprintln!("Error: {err:?}"); + Err(( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "Could not read file", + )) + } + } + } + Err(err) => { + eprintln!("Error: {err:?}"); + Err(( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "Could not find file", + )) + } + } } - }; - - let mime = mime_guess::from_path(path).first_or_octet_stream(); - let stream = tokio_util::io::ReaderStream::new(file); - - let rep = axum::response::Response::builder() - .header("content-type", mime.to_string()) - .header("accept-ranges", "bytes") - .header("content-length", file_size.to_string()) - .body(axum::body::Body::from_stream(stream)) - .unwrap(); - - Ok(rep) + Err(err) => { + eprintln!("Error: {err:?}"); + Err(( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "Could not find file", + )) + } + } } Err(_err) => Err(( axum::http::StatusCode::INTERNAL_SERVER_ERROR, @@ -347,8 +403,11 @@ pub mod endpoint { axum::Extension(pool): axum::Extension, axum::extract::Path(id): axum::extract::Path, ) -> (axum::http::StatusCode, axum::response::Response) { + let lab_config = crate::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; + match repo::song::get_song(&pool, &id).await { - Ok(song) => match simodels::song::io::to_data(&song) { + Ok(song) => match lr.download(&song.file_key).await { Ok(data) => { let bytes = axum::body::Bytes::from(data); let mut response = bytes.into_response(); @@ -397,6 +456,8 @@ pub mod endpoint { axum::Json, ) { let mut response = super::response::delete_song::Response::default(); + let lab_config = crate::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; match repo::song::get_song(&pool, &id).await { Ok(song) => { @@ -420,7 +481,7 @@ pub mod endpoint { match repo::coverart::delete_coverart(&pool, &coverart.id).await { Ok(deleted_coverart) => { - match song.remove_from_filesystem() { + match lr.delete(&song.file_key).await { Ok(_) => match coverart.remove_from_filesystem() { Ok(_) => { response.message = String::from( @@ -438,7 +499,7 @@ pub mod endpoint { } }, Err(err) => { - response.message = err.to_string(); + eprintln!("Error: {err:?}"); (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response)) } } diff --git a/src/main.rs b/src/main.rs index e07b4f6..3074e9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -500,10 +500,82 @@ mod tests { } mod sequence_flow { + pub const TEST_SONG_01_FILE_KEY: &str = "processed/song/track01.flac"; + pub const TEST_SONG_02_FILE_KEY: &str = "processed/song/track02.flac"; + + pub struct SongBucket { + pub file_key: String, + pub path: String, + } + + pub async fn upload_test_songs_to_bucket() { + let songs = vec![ + SongBucket { + file_key: TEST_SONG_01_FILE_KEY.to_string(), + path: "tests/I/track01.flac".to_string(), + }, + SongBucket { + file_key: TEST_SONG_02_FILE_KEY.to_string(), + path: "tests/I/track02.flac".to_string(), + }, + ]; + + let lab_config = soaricarus_api::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; + + for song in &songs { + let p = std::path::Path::new(&song.path); + match tokio::fs::File::open(p).await { + Ok(mut _file) => { + let data = labyrinth::Data { + filepath: song.path.clone(), + ..Default::default() + }; + + match lr.upload(&song.file_key, &data).await { + Ok(_) => {} + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + }; + } + } + + pub async fn delete_test_songs_from_bucket() { + let songs = vec![ + SongBucket { + file_key: TEST_SONG_01_FILE_KEY.to_string(), + path: "tests/I/track01.flac".to_string(), + }, + SongBucket { + file_key: TEST_SONG_02_FILE_KEY.to_string(), + path: "tests/I/track02.flac".to_string(), + }, + ]; + + let lab_config = soaricarus_api::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; + + for song in &songs { + match lr.delete(&song.file_key).await { + Ok(_) => {} + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + } + } + // Flow for queueing song pub async fn queue_song_flow( app: &axum::Router, ) -> Result<(axum::response::Response, uuid::Uuid), axum::http::Error> { + upload_test_songs_to_bucket().await; match super::request::song_queue_req(&app).await { Ok(response) => { let resp = super::util::get_resp_data::< @@ -637,8 +709,6 @@ mod tests { song ); - eprintln!("Song: {:?}", song); - match queue_coverart_flow(&app, &song_queue_id).await { Ok(response) => Ok((response, song_queue_id)), Err(err) => { @@ -777,6 +847,7 @@ mod tests { }; let _ = db_mgr::drop_database(&tm_pool, &db_name).await; + sequence_flow::delete_test_songs_from_bucket().await; } #[tokio::test] diff --git a/src/repo/song.rs b/src/repo/song.rs index fa70fd5..aa6a8b5 100644 --- a/src/repo/song.rs +++ b/src/repo/song.rs @@ -6,8 +6,8 @@ pub async fn insert( ) -> Result<(time::OffsetDateTime, uuid::Uuid), sqlx::Error> { let result = sqlx::query( r#" - INSERT INTO "song" (title, artist, album_artist, album, genre, year, track, disc, track_count, disc_count, duration, audio_type, filename, directory, user_id) - VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING date_created, id; + INSERT INTO "song" (title, artist, album_artist, album, genre, year, track, disc, track_count, disc_count, duration, audio_type, filename, directory, file_key, user_id) + VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING date_created, id; "# ) .bind(&song.title) @@ -24,6 +24,7 @@ pub async fn insert( .bind(&song.audio_type) .bind(&song.filename) .bind(&song.directory) + .bind(&song.file_key) .bind(song.user_id) .fetch_one(pool) .await @@ -133,6 +134,10 @@ pub async fn get_song( .try_get("directory") .map_err(|_e| sqlx::Error::RowNotFound) .unwrap(), + file_key: row + .try_get("file_key") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(), date_created: Some(date_created_time), user_id: row .try_get("user_id") @@ -228,6 +233,10 @@ pub async fn get_all_songs(pool: &sqlx::PgPool) -> Result