Wipe data from song queue (#135)

* Added TODOs for later

* Added endpoint to wipe data from song queue

* Migration changes

* Syntax error fix

* Added and linked endpoint

* Added test

* Warning fixes

* Code formatting
This commit was merged in pull request #135.
This commit is contained in:
KD
2025-05-25 21:12:17 -04:00
committed by GitHub
parent f0a0bee22b
commit f0d2b9de71
6 changed files with 230 additions and 1 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS "songQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
filename TEXT NOT NULL, filename TEXT NOT NULL,
status TEXT CHECK (status IN ('pending', 'processing', 'done')), status TEXT CHECK (status IN ('pending', 'processing', 'done')),
data BYTEA NOT NULL data BYTEA NULL
); );
-- Table to store queued metadata -- Table to store queued metadata
+1
View File
@@ -1,3 +1,4 @@
// TODO: Separate queue and coverart endpoints
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)] #[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct CoverArtQueue { pub struct CoverArtQueue {
pub id: uuid::Uuid, pub id: uuid::Uuid,
+1
View File
@@ -1,3 +1,4 @@
// TODO: Explicitly make this module target queueing a song's metadata
pub mod request { pub mod request {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
+1
View File
@@ -11,6 +11,7 @@ pub mod endpoints {
pub const QUEUECOVERART: &str = "/api/v2/coverart/queue"; pub const QUEUECOVERART: &str = "/api/v2/coverart/queue";
pub const QUEUECOVERARTDATA: &str = "/api/v2/coverart/queue/data"; pub const QUEUECOVERARTDATA: &str = "/api/v2/coverart/queue/data";
pub const QUEUECOVERARTLINK: &str = "/api/v2/coverart/queue/link"; pub const QUEUECOVERARTLINK: &str = "/api/v2/coverart/queue/link";
pub const QUEUESONGDATAWIPE: &str = "/api/v2/song/queue/data/wipe";
pub const CREATESONG: &str = "/api/v2/song"; pub const CREATESONG: &str = "/api/v2/song";
pub const CREATECOVERART: &str = "/api/v2/coverart"; pub const CREATECOVERART: &str = "/api/v2/coverart";
+106
View File
@@ -1,3 +1,4 @@
// TODO: Separate queue and song endpoints
pub mod request { pub mod request {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -76,6 +77,13 @@ pub mod request {
} }
} }
} }
pub mod wipe_data_from_song_queue {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Request {
pub song_queue_id: uuid::Uuid,
}
}
} }
pub mod response { pub mod response {
@@ -126,6 +134,14 @@ pub mod response {
pub data: Vec<icarus_models::song::Song>, pub data: Vec<icarus_models::song::Song>,
} }
} }
pub mod wipe_data_from_song_queue {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Response {
pub message: String,
pub data: Vec<uuid::Uuid>,
}
}
} }
// TODO: Might make a distinction between year and date in a song's tag at some point // TODO: Might make a distinction between year and date in a song's tag at some point
@@ -458,6 +474,66 @@ mod song_queue {
} }
} }
pub async fn get_song_queue(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
) -> Result<SongQueue, sqlx::Error> {
let result = sqlx::query(
r#"
SELECT id, filename, status FROM "songQueue" WHERE id = $1
"#,
)
.bind(id)
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error querying data: {:?}", e);
});
match result {
Ok(row) => Ok(SongQueue {
id: row
.try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(),
filename: row
.try_get("filename")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(),
status: row
.try_get("status")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(),
}),
Err(_err) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn wipe_data(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
) -> Result<uuid::Uuid, sqlx::Error> {
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<Vec<u8>, sqlx::Error> { pub async fn get_data(pool: &sqlx::PgPool, id: &uuid::Uuid) -> Result<Vec<u8>, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
@@ -735,4 +811,34 @@ pub mod endpoint {
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
} }
} }
pub async fn wipe_data_from_song_queue(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::Json(payload): axum::Json<super::request::wipe_data_from_song_queue::Request>,
) -> (
axum::http::StatusCode,
axum::Json<super::response::wipe_data_from_song_queue::Response>,
) {
let mut response = super::response::wipe_data_from_song_queue::Response::default();
let id = payload.song_queue_id;
match super::song_queue::get_song_queue(&pool, &id).await {
Ok(song_queue) => match super::song_queue::wipe_data(&pool, &song_queue.id).await {
Ok(wiped_id) => {
response.message = String::from("Success");
response.data.push(wiped_id);
(axum::http::StatusCode::OK, 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))
}
}
}
} }
+120
View File
@@ -73,6 +73,10 @@ pub mod init {
crate::callers::endpoints::QUEUESONGUPDATE, crate::callers::endpoints::QUEUESONGUPDATE,
patch(crate::callers::song::endpoint::update_song_queue), patch(crate::callers::song::endpoint::update_song_queue),
) )
.route(
crate::callers::endpoints::QUEUESONGDATAWIPE,
patch(crate::callers::song::endpoint::wipe_data_from_song_queue),
)
.route( .route(
crate::callers::endpoints::QUEUEMETADATA, crate::callers::endpoints::QUEUEMETADATA,
post(crate::callers::metadata::endpoint::queue_metadata), post(crate::callers::metadata::endpoint::queue_metadata),
@@ -1516,4 +1520,120 @@ mod tests {
let _ = db_mgr::drop_database(&tm_pool, &db_name).await; let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
} }
#[tokio::test]
async fn test_wipe_data_from_song_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::<crate::callers::song::response::Response>(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");
match queue_metadata_req(&app, &resp.data[0]).await {
Ok(response) => {
let resp =
get_resp_data::<crate::callers::song::response::Response>(response)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let id = resp.data[0];
match fetch_metadata_queue_req(&app, &id).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");
let song_q_id = resp.data[0].song_queue_id;
match create_song_req(&app, &song_q_id).await {
Ok(response) => {
let resp = get_resp_data::<crate::callers::song::response::create_metadata::Response>(response).await;
assert_eq!(
false,
resp.data.is_empty(),
"No songs found, Response {:?}",
resp
);
let song = &resp.data[0];
let song_id = song.id;
assert_eq!(
false,
song_id.is_nil(),
"Song id should not be nil {:?}",
song
);
let payload = serde_json::json!({
"song_queue_id": song_q_id
});
match app.clone().oneshot(
axum::http::Request::builder()
.method(axum::http::Method::PATCH)
.uri(crate::callers::endpoints::QUEUESONGDATAWIPE)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::from(payload.to_string()))
.unwrap()
).await {
Ok(response) => {
let resp = get_resp_data::<crate::callers::song::response::wipe_data_from_song_queue::Response>(response).await;
assert_eq!(
false,
resp.data.is_empty(),
"Failure in wiping data from song queue {:?}",
resp
);
let returned_id = &resp.data[0];
assert_eq!(false, returned_id.is_nil(), "Returned id should not be nil {:?}", returned_id);
assert_eq!(*returned_id, song_q_id, "Returned id does not match sent id {:?} {:?}", returned_id, song_q_id);
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
}
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;
}
} }