From d491767de44a4ff0bca8b2841e10377d480ba427 Mon Sep 17 00:00:00 2001 From: KD Date: Tue, 29 Apr 2025 21:52:22 -0400 Subject: [PATCH] Get metadata (#126) * Added time crate * Added endpoint to get metadata * Formatting * Added more code to get metadata Having issues returning response * Formatting * Migration change * Fix date issue * Got it somewhat working * Got things working * Code fix * Added test * Formatting * Test works * Cleanup * Migrations cleanup --- Cargo.toml | 3 +- migrations/20250420185217_init_migration.sql | 5 +- src/callers/metadata.rs | 160 ++++++++++++++++++- src/main.rs | 124 ++++++++++++-- 4 files changed, 272 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f11a69a..b891c3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,11 @@ tracing-subscriber = "0.3.19" futures = { version = "0.3.31" } uuid = { version = "1.16.0", features = ["v4", "serde"] } sqlx = { version = "0.8.5", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] } +time = { version = "0.3.41", features = ["formatting", "macros", "parsing", "serde"] } dotenvy = { version = "0.15.7" } icarus_meta = { git = "ssh://git@git.kundeng.us/phoenix/icarus_meta.git", tag = "v0.1.30" } icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.3" } [dev-dependencies] common-multipart-rfc7578 = { version = "0.7.0" } -url = { version = "2.5.4" } \ No newline at end of file +url = { version = "2.5.4" } diff --git a/migrations/20250420185217_init_migration.sql b/migrations/20250420185217_init_migration.sql index 6ea4c60..fd94f5f 100644 --- a/migrations/20250420185217_init_migration.sql +++ b/migrations/20250420185217_init_migration.sql @@ -9,12 +9,13 @@ CREATE TABLE IF NOT EXISTS "songQueue" ( data BYTEA NOT NULL ); +-- Table to store queued metadata CREATE TABLE IF NOT EXISTS "metadataQueue" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), metadata jsonb NOT NULL, - created_at timestamp DEFAULT now(), + created_at timestamptz DEFAULT now(), song_queue_id UUID NOT NULL ); -- Create an index for better query performance -CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata); \ No newline at end of file +CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata); diff --git a/src/callers/metadata.rs b/src/callers/metadata.rs index c16546d..253023f 100644 --- a/src/callers/metadata.rs +++ b/src/callers/metadata.rs @@ -1,7 +1,7 @@ pub mod request { use serde::{Deserialize, Serialize}; - #[derive(Default, Deserialize, Serialize)] + #[derive(Debug, Default, Deserialize, Serialize, sqlx::FromRow)] pub struct Request { pub id: uuid::Uuid, pub album: String, @@ -36,6 +36,16 @@ pub mod request { }) } } + + pub mod fetch_metadata { + #[derive( + Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, sqlx::Decode, + )] + pub struct Params { + pub id: Option, + pub song_queue_id: Option, + } + } } pub mod response { @@ -46,9 +56,19 @@ pub mod response { pub message: String, pub data: Vec, } + + pub mod fetch_metadata { + use serde::{Deserialize, Serialize}; + + #[derive(Default, Deserialize, Serialize)] + pub struct Response { + pub message: String, + pub data: Vec, + } + } } -mod metadata_queue { +pub mod metadata_queue { use sqlx::Row; #[derive(Debug, serde::Serialize, sqlx::FromRow)] @@ -56,6 +76,15 @@ mod metadata_queue { pub id: uuid::Uuid, } + #[derive(Debug, serde::Deserialize, serde::Serialize, sqlx::FromRow)] + pub struct MetadataQueue { + pub id: uuid::Uuid, + pub metadata: serde_json::Value, + #[serde(with = "time::serde::rfc3339")] + pub created_at: time::OffsetDateTime, + pub song_queue_id: uuid::Uuid, + } + pub async fn insert( pool: &sqlx::PgPool, metadata: &serde_json::Value, @@ -85,6 +114,87 @@ mod metadata_queue { Err(_err) => Err(sqlx::Error::RowNotFound), } } + + pub async fn get_with_song_queue_id( + pool: &sqlx::PgPool, + song_queue_id: &uuid::Uuid, + ) -> Result { + let result = sqlx::query( + r#" + SELECT * FROM "metadataQueue" WHERE song_queue_id = $1; + "#, + ) + .bind(song_queue_id) + .fetch_one(pool) + .await + .map_err(|e| { + eprintln!("Error inserting: {}", e); + }); + + match result { + Ok(row) => Ok(MetadataQueue { + id: row + .try_get("id") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(), + metadata: row + .try_get("metadata") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(), + created_at: row + .try_get("created_at") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(), + song_queue_id: row + .try_get("song_queue_id") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(), + }), + Err(_err) => Err(sqlx::Error::RowNotFound), + } + } + + pub async fn get_with_id( + pool: &sqlx::PgPool, + id: &uuid::Uuid, + ) -> Result { + let result = sqlx::query( + r#" + SELECT id, metadata, created_at, song_queue_id FROM "metadataQueue" WHERE id = $1; + "#, + ) + .bind(id) + .fetch_one(pool) + .await + .map_err(|e| { + eprintln!("Error inserting: {}", e); + }); + + match result { + Ok(row) => { + let data: serde_json::Value = row + .try_get("metadata") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(); + Ok(MetadataQueue { + id: row + .try_get("id") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(), + metadata: data, + created_at: row + .try_get("created_at") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(), + song_queue_id: row + .try_get("song_queue_id") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(), + }) + } + Err(_err) => Err(sqlx::Error::RowNotFound), + } + } } pub mod endpoint { @@ -115,4 +225,50 @@ pub mod endpoint { } } } + + pub async fn fetch_metadata( + axum::Extension(pool): axum::Extension, + axum::extract::Query(params): axum::extract::Query, + ) -> (StatusCode, Json) { + let mut response = super::response::fetch_metadata::Response::default(); + + match params.id { + Some(id) => { + println!("Something works {:?}", id); + + match super::metadata_queue::get_with_id(&pool, &id).await { + Ok(item) => { + response.message = String::from("Successful"); + response.data.push(item); + (StatusCode::OK, Json(response)) + } + Err(err) => { + response.message = err.to_string(); + (StatusCode::BAD_REQUEST, Json(response)) + } + } + } + _ => match params.song_queue_id { + Some(song_queue_id) => { + println!("Song queue Id is probably not nil"); + match super::metadata_queue::get_with_song_queue_id(&pool, &song_queue_id).await + { + Ok(item) => { + response.message = String::from("Successful"); + response.data.push(item); + (StatusCode::OK, Json(response)) + } + Err(err) => { + response.message = err.to_string(); + (StatusCode::BAD_REQUEST, Json(response)) + } + } + } + None => { + println!("What is going on?"); + (StatusCode::BAD_REQUEST, Json(response)) + } + }, + } + } } diff --git a/src/main.rs b/src/main.rs index 5a12085..560d5fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,6 +81,10 @@ pub mod init { crate::callers::endpoints::QUEUEMETADATA, post(crate::callers::metadata::endpoint::queue_metadata), ) + .route( + crate::callers::endpoints::QUEUEMETADATA, + get(crate::callers::metadata::endpoint::fetch_metadata), + ) } pub async fn app() -> axum::Router { @@ -275,6 +279,24 @@ mod tests { serde_json::from_slice(&body).unwrap() } + pub async fn payload_data(id: &uuid::Uuid) -> serde_json::Value { + serde_json::json!( + { + "id": id, + "album" : "Machine Gun: The FillMore East First Show", + "album_artist" : "Jimi Hendrix", + "artist" : "Jimi Hendrix", + "disc" : 1, + "disc_count" : 1, + "duration" : 330, + "genre" : "Psychadelic Rock", + "title" : "Power of Soul", + "track" : 1, + "track_count" : 11, + "year" : 2016 + }) + } + #[tokio::test] async fn test_song_queue() { let tm_pool = db_mgr::get_pool().await.unwrap(); @@ -451,21 +473,7 @@ mod tests { get_resp_data::(response).await; assert_eq!(false, resp.data.is_empty(), "Should not be empty"); assert_eq!(false, resp.data[0].is_nil(), "Should not be empty"); - let new_payload: serde_json::Value = serde_json::json!( - { - "id": resp.data[0], - "album" : "Machine Gun: The FillMore East First Show", - "album_artist" : "Jimi Hendrix", - "artist" : "Jimi Hendrix", - "disc" : 1, - "disc_count" : 1, - "duration" : 330, - "genre" : "Psychadelic Rock", - "title" : "Power of Soul", - "track" : 1, - "track_count" : 11, - "year" : 2016 - }); + let new_payload = payload_data(&resp.data[0]).await; match app .clone() @@ -497,4 +505,90 @@ mod tests { let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } + + #[tokio::test] + async fn test_get_metadata_queue() { + let tm_pool = db_mgr::get_pool().await.unwrap(); + let db_name = db_mgr::generate_db_name().await; + + match db_mgr::create_database(&tm_pool, &db_name).await { + Ok(_) => { + println!("Success"); + } + Err(err) => { + assert!(false, "Error: {:?}", err); + } + } + + let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); + db::migrations(&pool).await; + + let app = init::app(pool).await; + + // Send request + match song_queue_req(&app).await { + Ok(response) => { + let resp = + get_resp_data::(response).await; + assert_eq!(false, resp.data.is_empty(), "Should not be empty"); + assert_eq!(false, resp.data[0].is_nil(), "Should not be empty"); + let new_payload = payload_data(&resp.data[0]).await; + + match app + .clone() + .oneshot( + axum::http::Request::builder() + .method(axum::http::Method::POST) + .uri(crate::callers::endpoints::QUEUEMETADATA) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(axum::body::Body::from(new_payload.to_string())) + .unwrap(), + ) + .await + { + Ok(response) => { + let resp = + get_resp_data::(response) + .await; + assert_eq!(false, resp.data.is_empty(), "Should not be empty"); + + let id = resp.data[0]; + let uri = format!("{}?id={}", crate::callers::endpoints::QUEUEMETADATA, id); + + match app + .clone() + .oneshot( + axum::http::Request::builder() + .method(axum::http::Method::GET) + .uri(uri) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(axum::body::Body::empty()) + .unwrap(), + ) + .await + { + Ok(response) => { + let resp = get_resp_data::< + crate::callers::metadata::response::fetch_metadata::Response, + >(response) + .await; + assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); + } + Err(err) => { + assert!(false, "Error: {:?}", err); + } + } + } + Err(err) => { + assert!(false, "Error: {:?}", err); + } + } + } + Err(err) => { + assert!(false, "Error: {:?}", err); + } + }; + + let _ = db_mgr::drop_database(&tm_pool, &db_name).await; + } }