From bf88f249ea4c4257be8083cd4ffafe535e70e903 Mon Sep 17 00:00:00 2001 From: phoenix Date: Thu, 13 Aug 2026 15:27:31 -0400 Subject: [PATCH] Song queue (#258) Reviewed-on: http://git.kundeng.us/phoenix/soaricarus_api/pulls/258 --- Cargo.lock | 2 +- Cargo.toml | 2 +- LICENSE | 2 +- migrations/20250420185217_init_migration.sql | 9 +- src/callers/queue/song.rs | 238 +++++++++++-------- src/callers/song.rs | 78 +++--- src/lib.rs | 6 + src/main.rs | 182 +++++++------- src/repo/queue/data.rs | 98 ++++++++ src/repo/queue/mod.rs | 1 + src/repo/queue/song.rs | 52 +--- src/util/mod.rs | 17 ++ test_migrations/20250725213944_init.sql | 9 +- 13 files changed, 423 insertions(+), 273 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/repo/queue/data.rs create mode 100644 src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index baeb1f1..00274f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3022,7 +3022,7 @@ dependencies = [ [[package]] name = "soaricarus_api" -version = "0.5.3" +version = "0.5.4" dependencies = [ "axum", "axum-extra", diff --git a/Cargo.toml b/Cargo.toml index 59259dd..e503226 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soaricarus_api" -version = "0.5.3" +version = "0.5.4" edition = "2024" rust-version = "1.95" license = "MIT" diff --git a/LICENSE b/LICENSE index f5a66d1..c46460b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Kun Deng +Copyright (c) 2026 Kun Deng Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/migrations/20250420185217_init_migration.sql b/migrations/20250420185217_init_migration.sql index 1b67b0e..a50464d 100644 --- a/migrations/20250420185217_init_migration.sql +++ b/migrations/20250420185217_init_migration.sql @@ -6,10 +6,17 @@ CREATE TABLE IF NOT EXISTS "songQueue" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), filename TEXT NOT NULL, status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')), - data BYTEA NULL, user_id UUID NULL ); +CREATE TABLE IF NOT EXISTS "songQueueData" ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + file_key TEXT NOT NULL, + bucket TEXT NOT NULL, + region TEXT NOT NULL, + song_queue_id UUID NOT NULL +); + -- Table to store queued metadata CREATE TABLE IF NOT EXISTS "metadataQueue" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), diff --git a/src/callers/queue/song.rs b/src/callers/queue/song.rs index b0744c1..f5434f8 100644 --- a/src/callers/queue/song.rs +++ b/src/callers/queue/song.rs @@ -162,7 +162,6 @@ pub mod endpoint { if valid { match repo::song::insert( &pool, - &raw_data, &file_name, &crate::repo::queue::song::status::PENDING.to_string(), ) @@ -172,69 +171,33 @@ pub mod endpoint { results.push(queued_song); println!("Uploading to bucket"); - let s3_endpoint_url = - sienvy::environment::get_env("S3_ENDPOINT_URL"); - let bucket_name = - sienvy::environment::get_env("S3_BUCKET_NAME"); - let region = - sienvy::environment::get_env("GARAGE_S3_REGION"); - let access_key_id = sienvy::environment::get_env( - "GARAGE_DEFAULT_ACCESS_KEY", - ); - let secret_key = sienvy::environment::get_env( - "GARAGE_DEFAULT_SECRET_KEY", - ); - - let lab_config = labyrinth::config::Config { - url: s3_endpoint_url.value, - bucket: bucket_name.value, - region: region.value, - access_key_id: access_key_id.value, - secret_key: secret_key.value, - }; - - println!("Labyrinth config: {lab_config:?}"); + let lab_config = crate::util::maze::get_config(); let lr = labyrinth::Labyrinth { config: lab_config }; let data = labyrinth::Data { raw_data: copied_raw_data, ..Default::default() }; - let filename = simodels::song::generate_filename( - simodels::types::MusicType::FlacExtension, - true, - ) - .unwrap(); - println!("Filename: {filename:?}"); - let file_path = format!("queued/song/{filename}"); + + println!("Filename: {file_name:?}"); + let file_path = format!("queued/song/{file_name}"); println!("Path: {file_path:?}"); match lr.upload(&file_path, &data).await { - Ok(res) => { - println!("Result: {res:?}"); - - println!("Downloading file"); - match lr.download(&file_path).await { - Ok(res) => { - if res.is_empty() { - println!("This should not be empty"); - } else { - println!("Size: {:?}", res.len()); - println!("Going to delete file"); - - match lr.delete(&file_path).await { - Ok(res) => { - println!("Result: {res:?}"); - println!("Deleted"); - } - Err(err) => { - eprintln!("Error: {err:?}"); - } - } - } - } + Ok(_res) => { + match repo::data::insert( + &pool, + &file_path, + &lr.config.bucket, + &lr.config.region, + &queued_song, + ) + .await + { + Ok(_id) => {} Err(err) => { eprintln!("Error: {err:?}"); + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response)); } } } @@ -379,28 +342,40 @@ pub mod endpoint { )] pub async fn download_queued_song( axum::Extension(pool): axum::Extension, - axum::extract::Path(id): axum::extract::Path, + axum::extract::Path(song_queue_id): axum::extract::Path, ) -> (axum::http::StatusCode, axum::response::Response) { - println!("Id: {id}"); + println!("Id: {song_queue_id}"); - match repo::song::get_data(&pool, &id).await { - Ok(data) => { - let by = axum::body::Bytes::from(data); - let mut response = by.into_response(); - let headers = response.headers_mut(); - headers.insert( - axum::http::header::CONTENT_TYPE, - "audio/flac".parse().unwrap(), - ); - headers.insert( - axum::http::header::CONTENT_DISPOSITION, - format!("attachment; filename=\"{id}.flac\"") - .parse() - .unwrap(), - ); + let lab_config = crate::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; - (axum::http::StatusCode::OK, response) - } + match repo::data::get_with_song_queue_id(&pool, &song_queue_id).await { + Ok((id, file_key, _bucket, _region, _)) => match lr.download(&file_key).await { + Ok(data) => { + let by = axum::body::Bytes::from(data); + let mut response = by.into_response(); + let headers = response.headers_mut(); + headers.insert( + axum::http::header::CONTENT_TYPE, + "audio/flac".parse().unwrap(), + ); + headers.insert( + axum::http::header::CONTENT_DISPOSITION, + format!("attachment; filename=\"{id}.flac\"") + .parse() + .unwrap(), + ); + + (axum::http::StatusCode::OK, response) + } + Err(err) => { + eprintln!("Error: {err:?}"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::response::Response::default(), + ) + } + }, Err(_err) => ( axum::http::StatusCode::BAD_REQUEST, axum::response::Response::default(), @@ -488,7 +463,7 @@ pub mod endpoint { ) )] pub async fn update_song_queue( - axum::extract::Path(id): axum::extract::Path, + axum::extract::Path(song_queue_id): axum::extract::Path, axum::Extension(pool): axum::Extension, mut multipart: axum::extract::Multipart, ) -> ( @@ -519,15 +494,75 @@ pub mod endpoint { match super::is_song_valid(&raw_data).await { Ok(valid) => { if valid { - match repo::song::update(&pool, &raw_data, &id).await { - Ok(_) => { - response.message = - String::from(super::super::super::response::SUCCESSFUL); - response.data.push(id); - (axum::http::StatusCode::OK, axum::Json(response)) + let lab_config = crate::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; + + println!("Valid song"); + + match repo::data::get_with_song_queue_id(&pool, &song_queue_id).await { + Ok((id, file_key, _bucket, _region, _)) => { + match lr.delete(&file_key).await { + Ok(_response) => { + let data = labyrinth::Data { + raw_data, + ..Default::default() + }; + + let filename = simodels::song::generate_filename( + simodels::types::MusicType::FlacExtension, + true, + ) + .unwrap(); + let new_file_key = format!("queued/song/{filename}"); + println!("New key: {new_file_key:?}"); + + match lr.upload(&new_file_key, &data).await { + Ok(_) => { + match repo::data::update_file_key( + &pool, + &id, + &new_file_key, + ) + .await + { + Ok(_) => { + response.message = + super::super::super::response::SUCCESSFUL.to_string(); + response.data.push(song_queue_id); + ( + axum::http::StatusCode::OK, + axum::Json(response), + ) + } + Err(err) => { + response.message = err.to_string(); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response), + ) + } + } + } + Err(err) => { + eprintln!("Error: {err:?}"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response), + ) + } + } + } + Err(err) => { + eprintln!("Error: {err:?}"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response), + ) + } + } } Err(err) => { - response.message = err.to_string(); + eprintln!("Error: {err:?}"); ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response), @@ -564,7 +599,8 @@ pub mod endpoint { ), responses( (status = 200, description = "Queued song data wiped", body = super::response::wipe_data_from_song_queue::Response), - (status = 404, description = "Queued song cannot be found", body = super::response::wipe_data_from_song_queue::Response) + (status = 404, description = "Queued song cannot be found", body = super::response::wipe_data_from_song_queue::Response), + (status = 500, description = "Error wiping song data", body = super::response::wipe_data_from_song_queue::Response) ) )] pub async fn wipe_data_from_song_queue( @@ -575,21 +611,37 @@ pub mod endpoint { axum::Json, ) { let mut response = super::response::wipe_data_from_song_queue::Response::default(); - let id = payload.song_queue_id; + let song_queue_id = payload.song_queue_id; - match repo::song::get_song_queue(&pool, &id).await { - Ok(song_queue) => match repo::song::wipe_data(&pool, &song_queue.id).await { - Ok(wiped_id) => { - response.message = String::from("Success"); - response.data.push(wiped_id); + match repo::song::get_song_queue(&pool, &song_queue_id).await { + Ok(_song_queue) => { + match repo::data::get_with_song_queue_id(&pool, &song_queue_id).await { + Ok((_id, file_key, _bucket, _region, _)) => { + let lab_config = crate::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; - (axum::http::StatusCode::OK, axum::Json(response)) + match lr.delete(&file_key).await { + Ok(_) => { + response.message = String::from("Success"); + response.data.push(song_queue_id); + + (axum::http::StatusCode::OK, axum::Json(response)) + } + Err(err) => { + eprintln!("Error: {err:?}"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response), + ) + } + } + } + Err(err) => { + response.message = err.to_string(); + (axum::http::StatusCode::NOT_FOUND, axum::Json(response)) + } } - Err(err) => { - response.message = err.to_string(); - (axum::http::StatusCode::NOT_FOUND, axum::Json(response)) - } - }, + } Err(err) => { response.message = err.to_string(); (axum::http::StatusCode::NOT_FOUND, axum::Json(response)) diff --git a/src/callers/song.rs b/src/callers/song.rs index 1a2171c..656ae47 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -139,46 +139,56 @@ pub mod endpoint { .unwrap(); song.directory = sienvy::environment::get_root_directory().value; - match repo_queue::song::get_data(&pool, &payload.song_queue_id).await { - Ok(data) => { - song.data = data; - let dir = std::path::Path::new(&song.directory); - if !dir.exists() { - println!("Creating directory"); - match std::fs::create_dir_all(dir) { - Ok(_) => { - println!("Successfully created directory"); + let lab_config = crate::util::maze::get_config(); + let lr = labyrinth::Labyrinth { config: lab_config }; + + match repo_queue::data::get_with_song_queue_id(&pool, &payload.song_queue_id).await { + Ok((_id, file_key, _bucket, _region, _)) => match lr.download(&file_key).await { + Ok(data) => { + song.data = data; + let dir = std::path::Path::new(&song.directory); + if !dir.exists() { + println!("Creating directory"); + match std::fs::create_dir_all(dir) { + Ok(_) => { + println!("Successfully created directory"); + } + Err(err) => { + eprintln!("Error: Unable to create the directory {err:?}"); + } } + } + + match song.save_to_filesystem() { + Ok(_) => match repo::song::insert(&pool, &song).await { + Ok((date_created, id)) => { + song.id = id; + song.date_created = Some(date_created); + response.message = String::from("Successful"); + response.data.push(song); + + (axum::http::StatusCode::OK, axum::Json(response)) + } + Err(err) => { + response.message = + format!("{:?} song {:?}", err.to_string(), song); + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) + } + }, Err(err) => { - eprintln!("Error: Unable to create the directory {err:?}"); + response.message = err.to_string(); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response), + ) } } } - - match song.save_to_filesystem() { - Ok(_) => match repo::song::insert(&pool, &song).await { - Ok((date_created, id)) => { - song.id = id; - song.date_created = Some(date_created); - response.message = String::from("Successful"); - response.data.push(song); - - (axum::http::StatusCode::OK, axum::Json(response)) - } - Err(err) => { - response.message = format!("{:?} song {:?}", err.to_string(), song); - (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) - } - }, - Err(err) => { - response.message = err.to_string(); - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - axum::Json(response), - ) - } + Err(err) => { + eprintln!("Error: {err:?}"); + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) } - } + }, Err(err) => { response.message = err.to_string(); (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5301537 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +pub mod auth; +pub mod callers; +pub mod config; +pub mod db; +pub mod repo; +pub mod util; diff --git a/src/main.rs b/src/main.rs index b78eb13..e07b4f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,12 @@ -pub mod auth; -pub mod callers; -pub mod config; -pub mod db; -pub mod repo; - #[tokio::main] async fn main() { // initialize tracing tracing_subscriber::fmt::init(); - match tokio::net::TcpListener::bind(config::host::get_full()).await { + match tokio::net::TcpListener::bind(soaricarus_api::config::host::get_full()).await { Ok(listener) => { // build our application with routes - let app = config::init::app().await; + let app = soaricarus_api::config::init::app().await; axum::serve(listener, app).await.unwrap(); } Err(err) => { @@ -27,7 +21,7 @@ mod tests { use tower::ServiceExt; - use crate::db; + use soaricarus_api::db; mod db_mgr { use std::str::FromStr; @@ -111,7 +105,7 @@ mod tests { use std::time::Duration; pub async fn app(pool: sqlx::PgPool) -> axum::Router { - crate::config::init::routes() + soaricarus_api::config::init::routes() .await .layer(axum::Extension(pool)) .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) @@ -197,7 +191,7 @@ mod tests { "flac".to_string(), "tests/I/track01.flac".to_string(), ))), - crate::callers::queue::endpoints::QUEUESONG, + soaricarus_api::callers::queue::endpoints::QUEUESONG, axum::http::Method::POST, true, ) @@ -221,7 +215,7 @@ mod tests { match run_post( Some(ReqBody::Json(payload)), - crate::callers::queue::endpoints::QUEUESONGLINKUSERID, + soaricarus_api::callers::queue::endpoints::QUEUESONGLINKUSERID, axum::http::Method::PATCH, true, ) @@ -240,7 +234,7 @@ mod tests { ) -> Result { match run_post( None, - crate::callers::queue::endpoints::NEXTQUEUESONG, + soaricarus_api::callers::queue::endpoints::NEXTQUEUESONG, axum::http::Method::GET, false, ) @@ -260,7 +254,7 @@ mod tests { ) -> Result { let uri = format!( "{}?id={}", - crate::callers::queue::endpoints::QUEUEMETADATA, + soaricarus_api::callers::queue::endpoints::QUEUEMETADATA, id ); @@ -277,7 +271,7 @@ mod tests { app: &axum::Router, id: &uuid::Uuid, ) -> Result { - let raw_uri = String::from(crate::callers::queue::endpoints::QUEUESONGDATA); + let raw_uri = String::from(soaricarus_api::callers::queue::endpoints::QUEUESONGDATA); let end_index = raw_uri.len() - 4; let mut uri: String = (&raw_uri[..end_index]).to_string(); uri += &id.to_string(); @@ -299,7 +293,7 @@ mod tests { simeta::detection::coverart::constants::JPEG_TYPE.to_string(), "tests/I/Coverart-1.jpg".to_string(), ))), - crate::callers::queue::endpoints::QUEUECOVERART, + soaricarus_api::callers::queue::endpoints::QUEUECOVERART, axum::http::Method::POST, true, ) @@ -321,7 +315,7 @@ mod tests { match run_post( Some(ReqBody::Json(payload)), - crate::callers::queue::endpoints::QUEUEMETADATA, + soaricarus_api::callers::queue::endpoints::QUEUEMETADATA, axum::http::Method::POST, true, ) @@ -348,7 +342,7 @@ mod tests { match run_post( Some(ReqBody::Json(payload)), - crate::callers::queue::endpoints::QUEUECOVERARTLINK, + soaricarus_api::callers::queue::endpoints::QUEUECOVERARTLINK, axum::http::Method::PATCH, true, ) @@ -371,7 +365,7 @@ mod tests { match run_post( Some(ReqBody::Json(payload)), - crate::callers::endpoints::CREATECOVERART, + soaricarus_api::callers::endpoints::CREATECOVERART, axum::http::Method::POST, true, ) @@ -394,7 +388,7 @@ mod tests { match run_post( Some(ReqBody::Json(payload)), - crate::callers::endpoints::CREATESONG, + soaricarus_api::callers::endpoints::CREATESONG, axum::http::Method::POST, true, ) @@ -417,7 +411,7 @@ mod tests { match run_post( Some(ReqBody::Json(payload)), - crate::callers::queue::endpoints::QUEUESONG, + soaricarus_api::callers::queue::endpoints::QUEUESONG, axum::http::Method::PATCH, true, ) @@ -437,7 +431,7 @@ mod tests { ) -> Result { let uri = format!( "{}?id={}", - crate::callers::queue::endpoints::QUEUECOVERART, + soaricarus_api::callers::queue::endpoints::QUEUECOVERART, coverart_queue_id ); @@ -513,7 +507,7 @@ mod tests { match super::request::song_queue_req(&app).await { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::queue::song::response::song_queue::Response, + soaricarus_api::callers::queue::song::response::song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -527,7 +521,7 @@ mod tests { { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::queue::song::response::link_user_id::Response, + soaricarus_api::callers::queue::song::response::link_user_id::Response, >(response) .await; assert_eq!( @@ -539,7 +533,7 @@ mod tests { match super::request::queue_metadata_req(&app, &song_queue_id).await { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::queue::song::response::song_queue::Response, + soaricarus_api::callers::queue::song::response::song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -570,7 +564,7 @@ mod tests { match super::request::upload_coverart_queue_req(&app).await { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::queue::coverart::response::queue::Response, + soaricarus_api::callers::queue::coverart::response::queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -586,7 +580,7 @@ mod tests { { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::queue::coverart::response::link::Response, + soaricarus_api::callers::queue::coverart::response::link::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -616,7 +610,7 @@ mod tests { match queue_song_flow(&app).await { Ok((song_response, user_id)) => { let resp = super::util::get_resp_data::< - crate::callers::queue::metadata::response::fetch_metadata::Response, + soaricarus_api::callers::queue::metadata::response::fetch_metadata::Response, >(song_response) .await; assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); @@ -625,7 +619,7 @@ mod tests { match super::request::create_song_req(&app, &song_queue_id, &user_id).await { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::song::response::create_metadata::Response, + soaricarus_api::callers::song::response::create_metadata::Response, >(response) .await; assert_eq!( @@ -743,7 +737,7 @@ mod tests { ) -> serde_json::Value { serde_json::json!({ "id": song_queue_id, - "status": crate::repo::queue::song::status::READY + "status": soaricarus_api::repo::queue::song::status::READY }) } } @@ -771,7 +765,7 @@ mod tests { match request::song_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::song_queue::Response, + soaricarus_api::callers::queue::song::response::song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -807,7 +801,7 @@ mod tests { match request::song_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::song_queue::Response, + soaricarus_api::callers::queue::song::response::song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -820,7 +814,7 @@ mod tests { match request::song_queue_link_req(&app, &song_queue_id, &user_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::link_user_id::Response, + soaricarus_api::callers::queue::song::response::link_user_id::Response, >(response) .await; let collected_user_id = &resp.data[0]; @@ -872,20 +866,20 @@ mod tests { match sequence_flow::queue_song_and_coverart_flow(&app).await { Ok((resp_one, song_queue_id)) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::fetch_coverart_no_data::Response, + soaricarus_api::callers::queue::coverart::response::fetch_coverart_no_data::Response, >(resp_one) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); let _resp_coverart_queue_id = resp.data[0].id; - let old = crate::repo::queue::song::status::PENDING; - let target_status = crate::repo::queue::song::status::READY; + let old = soaricarus_api::repo::queue::song::status::PENDING; + let target_status = soaricarus_api::repo::queue::song::status::READY; match request::update_song_queue_status_req(&app, &song_queue_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::update_status::Response, + soaricarus_api::callers::queue::song::response::update_status::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -900,7 +894,7 @@ mod tests { match request::fetch_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::fetch_queue_song::Response, + soaricarus_api::callers::queue::song::response::fetch_queue_song::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -946,7 +940,7 @@ mod tests { match request::song_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::song_queue::Response, + soaricarus_api::callers::queue::song::response::song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -980,8 +974,9 @@ mod tests { } let songpath = song.song_path().unwrap(); - let raw_uri = - String::from(crate::callers::queue::endpoints::QUEUESONGUPDATE); + let raw_uri = String::from( + soaricarus_api::callers::queue::endpoints::QUEUESONGUPDATE, + ); let end_index = raw_uri.len() - 5; let uri = format!( @@ -1009,7 +1004,7 @@ mod tests { { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::update_song_queue::Response, + soaricarus_api::callers::queue::song::response::update_song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1066,7 +1061,7 @@ mod tests { match request::song_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::song_queue::Response, + soaricarus_api::callers::queue::song::response::song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1117,20 +1112,20 @@ mod tests { match sequence_flow::queue_song_and_coverart_flow(&app).await { Ok((resp_one, song_queue_id)) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::fetch_coverart_no_data::Response, + soaricarus_api::callers::queue::coverart::response::fetch_coverart_no_data::Response, >(resp_one) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); let _resp_coverart_queue_id = resp.data[0].id; - let old = crate::repo::queue::song::status::PENDING; - let done = crate::repo::queue::song::status::READY; + let old = soaricarus_api::repo::queue::song::status::PENDING; + let done = soaricarus_api::repo::queue::song::status::READY; match request::update_song_queue_status_req(&app, &song_queue_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::update_status::Response, + soaricarus_api::callers::queue::song::response::update_status::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1175,7 +1170,7 @@ mod tests { match request::song_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::song_queue::Response, + soaricarus_api::callers::queue::song::response::song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1184,7 +1179,7 @@ mod tests { match request::queue_metadata_req(&app, &resp.data[0]).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::song::response::song_queue::Response, + soaricarus_api::callers::queue::song::response::song_queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1225,7 +1220,7 @@ mod tests { match sequence_flow::queue_song_flow(&app).await { Ok((response, _user_id)) => { let resp = util::get_resp_data::< - crate::callers::queue::metadata::response::fetch_metadata::Response, + soaricarus_api::callers::queue::metadata::response::fetch_metadata::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); @@ -1261,7 +1256,7 @@ mod tests { match request::upload_coverart_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::queue::Response, + soaricarus_api::callers::queue::coverart::response::queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1298,7 +1293,7 @@ mod tests { match request::song_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::queue::Response, + soaricarus_api::callers::queue::coverart::response::queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1309,7 +1304,7 @@ mod tests { match request::upload_coverart_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::queue::Response, + soaricarus_api::callers::queue::coverart::response::queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1325,7 +1320,7 @@ mod tests { { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::link::Response, + soaricarus_api::callers::queue::coverart::response::link::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1379,7 +1374,7 @@ mod tests { match request::song_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::queue::Response, + soaricarus_api::callers::queue::coverart::response::queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1389,7 +1384,7 @@ mod tests { match sequence_flow::queue_coverart_flow(&app, &song_queue_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::fetch_coverart_no_data::Response, + soaricarus_api::callers::queue::coverart::response::fetch_coverart_no_data::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1429,7 +1424,7 @@ mod tests { match request::song_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::queue::Response, + soaricarus_api::callers::queue::coverart::response::queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1440,7 +1435,7 @@ mod tests { match request::upload_coverart_queue_req(&app).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::queue::Response, + soaricarus_api::callers::queue::coverart::response::queue::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1456,7 +1451,7 @@ mod tests { { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::link::Response, + soaricarus_api::callers::queue::coverart::response::link::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1471,7 +1466,7 @@ mod tests { ); let raw_uri = String::from( - crate::callers::queue::endpoints::QUEUECOVERARTDATA, + soaricarus_api::callers::queue::endpoints::QUEUECOVERARTDATA, ); let end_index = raw_uri.len() - 5; let uri = format!( @@ -1562,7 +1557,7 @@ mod tests { match sequence_flow::queue_song_flow(&app).await { Ok((response, user_id)) => { let resp = util::get_resp_data::< - crate::callers::queue::metadata::response::fetch_metadata::Response, + soaricarus_api::callers::queue::metadata::response::fetch_metadata::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); @@ -1571,7 +1566,7 @@ mod tests { match request::create_song_req(&app, &song_q_id, &user_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::song::response::create_metadata::Response, + soaricarus_api::callers::song::response::create_metadata::Response, >(response) .await; assert_eq!( @@ -1625,7 +1620,7 @@ mod tests { match sequence_flow::queue_song_flow(&app).await { Ok((response, user_id)) => { let resp = util::get_resp_data::< - crate::callers::queue::metadata::response::fetch_metadata::Response, + soaricarus_api::callers::queue::metadata::response::fetch_metadata::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); @@ -1634,7 +1629,7 @@ mod tests { match request::create_song_req(&app, &song_queue_id, &user_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::song::response::create_metadata::Response, + soaricarus_api::callers::song::response::create_metadata::Response, >(response) .await; assert_eq!( @@ -1655,7 +1650,7 @@ mod tests { match sequence_flow::queue_coverart_flow(&app, &song_queue_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::fetch_coverart_no_data::Response, + soaricarus_api::callers::queue::coverart::response::fetch_coverart_no_data::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1670,7 +1665,7 @@ mod tests { { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::coverart::response::create_coverart::Response, + soaricarus_api::callers::coverart::response::create_coverart::Response, >(response) .await; assert_eq!( @@ -1725,7 +1720,7 @@ mod tests { match sequence_flow::queue_song_flow(&app).await { Ok((response, user_id)) => { let resp = util::get_resp_data::< - crate::callers::queue::metadata::response::fetch_metadata::Response, + soaricarus_api::callers::queue::metadata::response::fetch_metadata::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); @@ -1734,7 +1729,7 @@ mod tests { match request::create_song_req(&app, &song_q_id, &user_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::song::response::create_metadata::Response, + soaricarus_api::callers::song::response::create_metadata::Response, >(response) .await; assert_eq!( @@ -1761,7 +1756,7 @@ mod tests { .oneshot( request::run_post( Some(request::ReqBody::Json(payload)), - crate::callers::queue::endpoints::QUEUESONGDATAWIPE, + soaricarus_api::callers::queue::endpoints::QUEUESONGDATAWIPE, axum::http::Method::PATCH, true, ) @@ -1771,7 +1766,7 @@ mod tests { .await { Ok(response) => { - let resp = util::get_resp_data::(response).await; + let resp = util::get_resp_data::(response).await; assert_eq!( false, resp.data.is_empty(), @@ -1832,7 +1827,7 @@ mod tests { match sequence_flow::queue_song_flow(&app).await { Ok((response, user_id)) => { let resp = util::get_resp_data::< - crate::callers::queue::metadata::response::fetch_metadata::Response, + soaricarus_api::callers::queue::metadata::response::fetch_metadata::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); @@ -1841,7 +1836,7 @@ mod tests { match request::create_song_req(&app, &song_queue_id, &user_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::song::response::create_metadata::Response, + soaricarus_api::callers::song::response::create_metadata::Response, >(response) .await; assert_eq!( @@ -1864,7 +1859,7 @@ mod tests { match sequence_flow::queue_coverart_flow(&app, &song_queue_id).await { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::fetch_coverart_no_data::Response, + soaricarus_api::callers::queue::coverart::response::fetch_coverart_no_data::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1880,7 +1875,7 @@ mod tests { .oneshot( request::run_post( Some(request::ReqBody::Json(payload)), - crate::callers::queue::endpoints::QUEUECOVERARTDATAWIPE, + soaricarus_api::callers::queue::endpoints::QUEUECOVERARTDATAWIPE, axum::http::Method::PATCH, true, ) @@ -1891,7 +1886,7 @@ mod tests { { Ok(response) => { let resp = util::get_resp_data::< - crate::callers::queue::coverart::response::wipe_data_from_coverart_queue::Response, + soaricarus_api::callers::queue::coverart::response::wipe_data_from_coverart_queue::Response, >(response) .await; assert_eq!( @@ -1948,7 +1943,7 @@ mod tests { let (id, _, _, _) = test_data::song_id().await.unwrap(); - let uri = format!("{}?id={id}", crate::callers::endpoints::GETSONGS); + let uri = format!("{}?id={id}", soaricarus_api::callers::endpoints::GETSONGS); match app .clone() @@ -1961,7 +1956,7 @@ mod tests { { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::song::response::get_songs::Response, + soaricarus_api::callers::song::response::get_songs::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -1998,7 +1993,10 @@ mod tests { let id = test_data::coverart_id().await.unwrap(); - let uri = format!("{}?id={id}", crate::callers::endpoints::GETCOVERART); + let uri = format!( + "{}?id={id}", + soaricarus_api::callers::endpoints::GETCOVERART + ); match app .clone() @@ -2011,7 +2009,7 @@ mod tests { { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::coverart::response::get_coverart::Response, + soaricarus_api::callers::coverart::response::get_coverart::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); @@ -2078,7 +2076,7 @@ mod tests { let (id, _, _, _) = test_data::song_id().await.unwrap(); - let my_url = crate::callers::endpoints::STREAMSONG; + let my_url = soaricarus_api::callers::endpoints::STREAMSONG; let last = my_url.len() - 5; let uri = format!("{}/{id}", &my_url[0..last]); @@ -2132,9 +2130,11 @@ mod tests { let (id, _, _, _) = test_data::song_id().await.unwrap(); - let uri = - super::util::format_url_with_value(crate::callers::endpoints::DOWNLOADSONG, &id) - .await; + let uri = super::util::format_url_with_value( + soaricarus_api::callers::endpoints::DOWNLOADSONG, + &id, + ) + .await; match app .clone() @@ -2187,7 +2187,7 @@ mod tests { let id = test_data::coverart_id().await.unwrap(); let uri = super::util::format_url_with_value( - crate::callers::endpoints::DOWNLOADCOVERART, + soaricarus_api::callers::endpoints::DOWNLOADCOVERART, &id, ) .await; @@ -2309,9 +2309,11 @@ mod tests { .await .unwrap(); - let uri = - super::util::format_url_with_value(crate::callers::endpoints::DELETESONG, &id) - .await; + let uri = super::util::format_url_with_value( + soaricarus_api::callers::endpoints::DELETESONG, + &id, + ) + .await; match app .clone() @@ -2324,7 +2326,7 @@ mod tests { { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::song::response::delete_song::Response, + soaricarus_api::callers::song::response::delete_song::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Response has no data"); @@ -2384,7 +2386,7 @@ mod tests { .oneshot( super::request::run_post( None, - crate::callers::endpoints::GETALLSONGS, + soaricarus_api::callers::endpoints::GETALLSONGS, axum::http::Method::GET, false, ) @@ -2395,7 +2397,7 @@ mod tests { { Ok(response) => { let resp = super::util::get_resp_data::< - crate::callers::song::response::get_songs::Response, + soaricarus_api::callers::song::response::get_songs::Response, >(response) .await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); diff --git a/src/repo/queue/data.rs b/src/repo/queue/data.rs new file mode 100644 index 0000000..ed0afb6 --- /dev/null +++ b/src/repo/queue/data.rs @@ -0,0 +1,98 @@ +use sqlx::Row; + +pub async fn insert( + pool: &sqlx::PgPool, + file_key: &str, + bucket: &str, + region: &str, + song_queue_id: &uuid::Uuid, +) -> Result { + match sqlx::query( + r#" + INSERT INTO "songQueueData" (file_key, bucket, region, song_queue_id) VALUES($1, $2, $3, $4) RETURNING id; + "#, + ) + .bind(file_key) + .bind(bucket) + .bind(region) + .bind(song_queue_id) + .fetch_one(pool).await { + Ok(row) => { + let id: uuid::Uuid = row.try_get("id")?; + Ok(id) + } + Err(_err) => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn update_file_key( + pool: &sqlx::PgPool, + id: &uuid::Uuid, + file_key: &str, +) -> Result<(), sqlx::Error> { + match sqlx::query( + r#" + UPDATE "songQueueData" SET file_key = $1 WHERE id = $2; + "#, + ) + .bind(file_key) + .bind(id) + .execute(pool) + .await + { + Ok(_row) => Ok(()), + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get( + pool: &sqlx::PgPool, + id: &uuid::Uuid, +) -> Result<(uuid::Uuid, String, String, String, uuid::Uuid), sqlx::Error> { + match sqlx::query( + r#" + SELECT id, file_key, bucket, region, song_queue_id FROM "songQueueData" + WHERE id = $1 + "#, + ) + .bind(id) + .fetch_one(pool) + .await + { + Ok(row) => { + let file_key: String = row.try_get("file_key")?; + let bucket: String = row.try_get("bucket")?; + let region: String = row.try_get("region")?; + let song_queue_id: uuid::Uuid = row.try_get("song_queue_id")?; + + Ok((*id, file_key, bucket, region, song_queue_id)) + } + Err(err) => Err(err), + } +} + +pub async fn get_with_song_queue_id( + pool: &sqlx::PgPool, + song_queue_id: &uuid::Uuid, +) -> Result<(uuid::Uuid, String, String, String, uuid::Uuid), sqlx::Error> { + match sqlx::query( + r#" + SELECT id, file_key, bucket, region, song_queue_id FROM "songQueueData" + WHERE song_queue_id = $1 + "#, + ) + .bind(song_queue_id) + .fetch_one(pool) + .await + { + Ok(row) => { + let id: uuid::Uuid = row.try_get("id")?; + let file_key: String = row.try_get("file_key")?; + let bucket: String = row.try_get("bucket")?; + let region: String = row.try_get("region")?; + + Ok((id, file_key, bucket, region, *song_queue_id)) + } + Err(err) => Err(err), + } +} diff --git a/src/repo/queue/mod.rs b/src/repo/queue/mod.rs index 67284cf..28bd3e4 100644 --- a/src/repo/queue/mod.rs +++ b/src/repo/queue/mod.rs @@ -1,3 +1,4 @@ pub mod coverart; +pub mod data; pub mod metadata; pub mod song; diff --git a/src/repo/queue/song.rs b/src/repo/queue/song.rs index 4b1577e..01a5922 100644 --- a/src/repo/queue/song.rs +++ b/src/repo/queue/song.rs @@ -24,16 +24,14 @@ pub mod dbtype { pub async fn insert( pool: &sqlx::PgPool, - data: &Vec, filename: &String, status: &String, ) -> Result { let result = sqlx::query( r#" - INSERT INTO "songQueue" (data, filename, status) VALUES($1, $2, $3) RETURNING id; + INSERT INTO "songQueue" (filename, status) VALUES($1, $2) RETURNING id; "#, ) - .bind(data) .bind(filename) .bind(status) .fetch_one(pool) @@ -253,51 +251,3 @@ pub async fn get_song_queue( Err(_err) => Err(sqlx::Error::RowNotFound), } } - -pub async fn wipe_data(pool: &sqlx::PgPool, id: &uuid::Uuid) -> Result { - let result = sqlx::query( - r#" - UPDATE "songQueue" SET data = NULL WHERE id = $1 RETURNING id; - "#, - ) - .bind(id) - .fetch_one(pool) - .await - .map_err(|e| { - eprintln!("Error updating record: {e}"); - }); - - match result { - Ok(row) => Ok(row - .try_get("id") - .map_err(|_e| sqlx::Error::RowNotFound) - .unwrap()), - Err(_) => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_data(pool: &sqlx::PgPool, id: &uuid::Uuid) -> Result, sqlx::Error> { - let result = sqlx::query( - r#" - SELECT data FROM "songQueue" - WHERE id = $1; - "#, - ) - .bind(id) - .fetch_one(pool) - .await - .map_err(|e| { - eprintln!("Error inserting: {e}"); - }); - - match result { - Ok(row) => { - let data = row - .try_get("data") - .map_err(|_e| sqlx::Error::RowNotFound) - .unwrap(); - Ok(data) - } - Err(_err) => Err(sqlx::Error::RowNotFound), - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..0796dd1 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,17 @@ +pub mod maze { + pub fn get_config() -> labyrinth::config::Config { + let s3_endpoint_url = sienvy::environment::get_env("S3_ENDPOINT_URL"); + let bucket_name = sienvy::environment::get_env("S3_BUCKET_NAME"); + let region = sienvy::environment::get_env("GARAGE_S3_REGION"); + let access_key_id = sienvy::environment::get_env("GARAGE_DEFAULT_ACCESS_KEY"); + let secret_key = sienvy::environment::get_env("GARAGE_DEFAULT_SECRET_KEY"); + + labyrinth::config::Config { + url: s3_endpoint_url.value, + bucket: bucket_name.value, + region: region.value, + access_key_id: access_key_id.value, + secret_key: secret_key.value, + } + } +} diff --git a/test_migrations/20250725213944_init.sql b/test_migrations/20250725213944_init.sql index c4c922f..0f66046 100644 --- a/test_migrations/20250725213944_init.sql +++ b/test_migrations/20250725213944_init.sql @@ -6,10 +6,17 @@ CREATE TABLE IF NOT EXISTS "songQueue" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), filename TEXT NOT NULL, status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')), - data BYTEA NULL, user_id UUID NULL ); +CREATE TABLE IF NOT EXISTS "songQueueData" ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + file_key TEXT NOT NULL, + bucket TEXT NOT NULL, + region TEXT NOT NULL, + song_queue_id UUID NOT NULL +); + -- Table to store queued metadata CREATE TABLE IF NOT EXISTS "metadataQueue" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(),