From 8123553e006df3aca8d43a751a52d3bcbc996198 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 15:50:19 -0400 Subject: [PATCH 01/28] Added migration for metadata table used to store a song's metadata --- migrations/20250420185217_init_migration.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/migrations/20250420185217_init_migration.sql b/migrations/20250420185217_init_migration.sql index aca722f..84d3ae8 100644 --- a/migrations/20250420185217_init_migration.sql +++ b/migrations/20250420185217_init_migration.sql @@ -26,3 +26,18 @@ CREATE TABLE IF NOT EXISTS "coverartQueue" ( -- Create an index for better query performance CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata); + +CREATE TABLE IF NOT EXISTS "metadata" ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + title TEXT NOT NULL, + artist TEXT NOT NULL, + album_artist TEXT NOT NULL, + album TEXT NOT NULL, + genre TEXT NOT NULL, + date TEXT NOT NULL, + track SMALLINT NOT NULL, + disc SMALLINT NOT NULL, + track_count SMALLINT NOT NULL, + disc_count SMALLINT NOT NULL, + duration INT NOT NULL +); -- 2.47.3 From dace046d7e761c42672d45bcc2ee84cc02b4e3e5 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 15:50:52 -0400 Subject: [PATCH 02/28] Added comment for new table --- migrations/20250420185217_init_migration.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/migrations/20250420185217_init_migration.sql b/migrations/20250420185217_init_migration.sql index 84d3ae8..c1a3906 100644 --- a/migrations/20250420185217_init_migration.sql +++ b/migrations/20250420185217_init_migration.sql @@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS "coverartQueue" ( -- Create an index for better query performance CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata); +-- Table to store a song's metadata CREATE TABLE IF NOT EXISTS "metadata" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL, -- 2.47.3 From 2eaede9f0be6feea252fd49fa2b33350aaa61de9 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 15:56:24 -0400 Subject: [PATCH 03/28] Added endpoint to create metadata --- src/callers/metadata.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/callers/metadata.rs b/src/callers/metadata.rs index 253023f..841a4f5 100644 --- a/src/callers/metadata.rs +++ b/src/callers/metadata.rs @@ -66,6 +66,13 @@ pub mod response { pub data: Vec, } } + + pub mod create_metadata { + #[derive(Default, serde::Deserialize, serde::Serialize)] + pub struct Response { + pub message: String, + } + } } pub mod metadata_queue { @@ -271,4 +278,12 @@ pub mod endpoint { }, } } + + // TODO: Implement + pub async fn create_metadata( + ) -> (axum::http::StatusCode, axum::Json) { + let mut response = super::response::create_metadata::Response::default(); + + (axum::http::StatusCode::OK, axum::Json(response)) + } } -- 2.47.3 From 9d68b54c94095dc7bc94647d9e5af5b03b8d519a Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 16:16:43 -0400 Subject: [PATCH 04/28] Filling out code --- src/callers/metadata.rs | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/callers/metadata.rs b/src/callers/metadata.rs index 841a4f5..3265831 100644 --- a/src/callers/metadata.rs +++ b/src/callers/metadata.rs @@ -46,8 +46,36 @@ pub mod request { pub song_queue_id: Option, } } + + pub mod create_metadata { + #[derive(Debug, serde::Deserialize, serde::Serialize)] + pub struct Request { + pub title: String, + pub artist: String, + pub album_artist: String, + pub album: String, + pub genre: String, + pub date: String, + pub track: i32, + pub disc: i32, + pub track_count: i32, + pub disc_count: i32, + pub duration: i64, + } + + impl Request { + pub fn is_valid(&self) -> bool { + !self.title.is_empty() || !self.artist.is_empty() || !self.album_artist.is_empty() + || !self.album.is_empty() || !self.genre.is_empty() || !self.date.is_empty() + || self.track > 0 || self.disc > 0 || self.track_count > 0 || self.disc_count > 0 + || self.duration > 0 + } + } + } } +// TODO: Might make a distinction between year and date in a song's tag at some point + pub mod response { use serde::{Deserialize, Serialize}; @@ -204,6 +232,9 @@ pub mod metadata_queue { } } +pub mod metadata { +} + pub mod endpoint { use axum::{Json, http::StatusCode}; @@ -281,9 +312,16 @@ pub mod endpoint { // TODO: Implement pub async fn create_metadata( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, ) -> (axum::http::StatusCode, axum::Json) { let mut response = super::response::create_metadata::Response::default(); - (axum::http::StatusCode::OK, axum::Json(response)) + if payload.is_valid() { + (axum::http::StatusCode::OK, axum::Json(response)) + } else { + response.message = String::from("Request body is not valid"); + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) + } } } -- 2.47.3 From 246e6510c1a843f770a3ae883a210f2213108980 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 16:17:20 -0400 Subject: [PATCH 05/28] Changed type of duration in request --- src/callers/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/callers/metadata.rs b/src/callers/metadata.rs index 3265831..93bc5bf 100644 --- a/src/callers/metadata.rs +++ b/src/callers/metadata.rs @@ -60,7 +60,7 @@ pub mod request { pub disc: i32, pub track_count: i32, pub disc_count: i32, - pub duration: i64, + pub duration: i32, } impl Request { -- 2.47.3 From ec1dacf347a48c51f2fedc4bf87cdd84b5aa84b9 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 16:22:19 -0400 Subject: [PATCH 06/28] Reorganizing code --- src/callers/metadata.rs | 53 ----------------------------------------- src/callers/song.rs | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 53 deletions(-) diff --git a/src/callers/metadata.rs b/src/callers/metadata.rs index 93bc5bf..253023f 100644 --- a/src/callers/metadata.rs +++ b/src/callers/metadata.rs @@ -46,36 +46,8 @@ pub mod request { pub song_queue_id: Option, } } - - pub mod create_metadata { - #[derive(Debug, serde::Deserialize, serde::Serialize)] - pub struct Request { - pub title: String, - pub artist: String, - pub album_artist: String, - pub album: String, - pub genre: String, - pub date: String, - pub track: i32, - pub disc: i32, - pub track_count: i32, - pub disc_count: i32, - pub duration: i32, - } - - impl Request { - pub fn is_valid(&self) -> bool { - !self.title.is_empty() || !self.artist.is_empty() || !self.album_artist.is_empty() - || !self.album.is_empty() || !self.genre.is_empty() || !self.date.is_empty() - || self.track > 0 || self.disc > 0 || self.track_count > 0 || self.disc_count > 0 - || self.duration > 0 - } - } - } } -// TODO: Might make a distinction between year and date in a song's tag at some point - pub mod response { use serde::{Deserialize, Serialize}; @@ -94,13 +66,6 @@ pub mod response { pub data: Vec, } } - - pub mod create_metadata { - #[derive(Default, serde::Deserialize, serde::Serialize)] - pub struct Response { - pub message: String, - } - } } pub mod metadata_queue { @@ -232,9 +197,6 @@ pub mod metadata_queue { } } -pub mod metadata { -} - pub mod endpoint { use axum::{Json, http::StatusCode}; @@ -309,19 +271,4 @@ pub mod endpoint { }, } } - - // TODO: Implement - pub async fn create_metadata( - axum::Extension(pool): axum::Extension, - axum::Json(payload): axum::Json, - ) -> (axum::http::StatusCode, axum::Json) { - let mut response = super::response::create_metadata::Response::default(); - - if payload.is_valid() { - (axum::http::StatusCode::OK, axum::Json(response)) - } else { - response.message = String::from("Request body is not valid"); - (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) - } - } } diff --git a/src/callers/song.rs b/src/callers/song.rs index b4ddeef..9210fee 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -13,6 +13,32 @@ pub mod request { pub status: String, } } + + pub mod create_metadata { + #[derive(Debug, serde::Deserialize, serde::Serialize)] + pub struct Request { + pub title: String, + pub artist: String, + pub album_artist: String, + pub album: String, + pub genre: String, + pub date: String, + pub track: i32, + pub disc: i32, + pub track_count: i32, + pub disc_count: i32, + pub duration: i32, + } + + impl Request { + pub fn is_valid(&self) -> bool { + !self.title.is_empty() || !self.artist.is_empty() || !self.album_artist.is_empty() + || !self.album.is_empty() || !self.genre.is_empty() || !self.date.is_empty() + || self.track > 0 || self.disc > 0 || self.track_count > 0 || self.disc_count > 0 + || self.duration > 0 + } + } + } } pub mod response { @@ -55,8 +81,17 @@ pub mod response { pub data: Vec>, } } + + pub mod create_metadata { + #[derive(Default, serde::Deserialize, serde::Serialize)] + pub struct Response { + pub message: String, + } + } } +// TODO: Might make a distinction between year and date in a song's tag at some point + pub mod status { pub const PENDING: &str = "pending"; pub const PROCESSING: &str = "processing"; @@ -456,4 +491,19 @@ pub mod endpoint { (axum::http::StatusCode::NOT_FOUND, axum::Json(response)) } } + + // TODO: Implement + pub async fn create_metadata( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, + ) -> (axum::http::StatusCode, axum::Json) { + let mut response = super::response::create_metadata::Response::default(); + + if payload.is_valid() { + (axum::http::StatusCode::OK, axum::Json(response)) + } else { + response.message = String::from("Request body is not valid"); + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) + } + } } -- 2.47.3 From 0a1b6e2ecdf4d03dedc4e80478d170205f07be4e Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 16:26:21 -0400 Subject: [PATCH 07/28] Changing metadata table to song --- migrations/20250420185217_init_migration.sql | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/migrations/20250420185217_init_migration.sql b/migrations/20250420185217_init_migration.sql index c1a3906..8428205 100644 --- a/migrations/20250420185217_init_migration.sql +++ b/migrations/20250420185217_init_migration.sql @@ -27,8 +27,8 @@ CREATE TABLE IF NOT EXISTS "coverartQueue" ( -- Create an index for better query performance CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata); --- Table to store a song's metadata -CREATE TABLE IF NOT EXISTS "metadata" ( +-- Table to store a song's info +CREATE TABLE IF NOT EXISTS "song" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL, artist TEXT NOT NULL, @@ -36,9 +36,15 @@ CREATE TABLE IF NOT EXISTS "metadata" ( album TEXT NOT NULL, genre TEXT NOT NULL, date TEXT NOT NULL, + year INT NOT NULL, track SMALLINT NOT NULL, disc SMALLINT NOT NULL, track_count SMALLINT NOT NULL, disc_count SMALLINT NOT NULL, - duration INT NOT NULL + duration INT NOT NULL, + audio_type TEXT NOT NULL, + date_created timestamptz DEFAULT now(), + filename TEXT NOT NULL, + directory TEXT NOT NULL, + user_id UUID NULL, ); -- 2.47.3 From 1d3ced50ac92707be941488dd95f67f902db07b0 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 16:29:02 -0400 Subject: [PATCH 08/28] Added TODO for data type discrepancy for the future --- migrations/20250420185217_init_migration.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/migrations/20250420185217_init_migration.sql b/migrations/20250420185217_init_migration.sql index 8428205..16f5b7a 100644 --- a/migrations/20250420185217_init_migration.sql +++ b/migrations/20250420185217_init_migration.sql @@ -35,7 +35,8 @@ CREATE TABLE IF NOT EXISTS "song" ( album_artist TEXT NOT NULL, album TEXT NOT NULL, genre TEXT NOT NULL, - date TEXT NOT NULL, + -- TODO: Address discrepancy of date and year at some point + -- date TEXT NOT NULL, year INT NOT NULL, track SMALLINT NOT NULL, disc SMALLINT NOT NULL, -- 2.47.3 From ff9b5c0caef69100def0ccec951b4de3af9ea301 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 16:29:30 -0400 Subject: [PATCH 09/28] Fix migration syntax error --- migrations/20250420185217_init_migration.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/20250420185217_init_migration.sql b/migrations/20250420185217_init_migration.sql index 16f5b7a..22d78b0 100644 --- a/migrations/20250420185217_init_migration.sql +++ b/migrations/20250420185217_init_migration.sql @@ -47,5 +47,5 @@ CREATE TABLE IF NOT EXISTS "song" ( date_created timestamptz DEFAULT now(), filename TEXT NOT NULL, directory TEXT NOT NULL, - user_id UUID NULL, + user_id UUID NULL ); -- 2.47.3 From 21b22c3c19b05cd523abebc4cd19c19ad4a115a8 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 16:39:51 -0400 Subject: [PATCH 10/28] Added types to request --- src/callers/song.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index 9210fee..98f0100 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -28,6 +28,8 @@ pub mod request { pub track_count: i32, pub disc_count: i32, pub duration: i32, + pub audio_type: String, + pub user_id: uuid::Uuid, } impl Request { @@ -35,7 +37,7 @@ pub mod request { !self.title.is_empty() || !self.artist.is_empty() || !self.album_artist.is_empty() || !self.album.is_empty() || !self.genre.is_empty() || !self.date.is_empty() || self.track > 0 || self.disc > 0 || self.track_count > 0 || self.disc_count > 0 - || self.duration > 0 + || self.duration > 0 || !self.audio_type.is_empty() || !self.user_id.is_nil() } } } -- 2.47.3 From acd95fbbc6a60f547c6e54be8ab800e8db363e21 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 16:57:59 -0400 Subject: [PATCH 11/28] Added environment variable for root directory: --- .env.sample | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 57c9f47..33ebf08 100644 --- a/.env.sample +++ b/.env.sample @@ -1,6 +1,7 @@ SECRET_MAIN_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h +ROOT_DIRECTORY=/home/icarus/mydata POSTGRES_MAIN_USER=icarus POSTGRES_MAIN_PASSWORD=password POSTGRES_MAIN_DB=icarus_db POSTGRES_MAIN_HOST=main_db -DATABASE_URL=postgres://${POSTGRES_MAIN_USER}:${POSTGRES_MAIN_PASSWORD}@${POSTGRES_MAIN_HOST}:5432/${POSTGRES_MAIN_DB} \ No newline at end of file +DATABASE_URL=postgres://${POSTGRES_MAIN_USER}:${POSTGRES_MAIN_PASSWORD}@${POSTGRES_MAIN_HOST}:5432/${POSTGRES_MAIN_DB} -- 2.47.3 From 1e4d36fb65a407bf1b9a36b22900849f2e34937b Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 17:04:30 -0400 Subject: [PATCH 12/28] Moving keys to its own module --- src/keys.rs | 5 +++++ src/main.rs | 19 +++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 src/keys.rs diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 0000000..b58a50b --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,5 @@ +pub const DBURL: &str = "DATABASE_URL"; + +pub mod error { + pub const ERROR: &str = "DATABASE_URL must be set in .env"; +} diff --git a/src/main.rs b/src/main.rs index 038c9fc..e314954 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,17 @@ pub mod callers; +pub mod keys; pub mod db { use sqlx::postgres::PgPoolOptions; use std::env; + + use crate::keys; pub mod connection_settings { pub const MAXCONN: u32 = 10; } - pub mod keys { - pub const DBURL: &str = "DATABASE_URL"; - - pub mod error { - pub const ERROR: &str = "DATABASE_URL must be set in .env"; - } - } - pub async fn create_pool() -> Result { let database_url = get_db_url().await; println!("Database url: {:?}", database_url); @@ -157,13 +152,13 @@ mod tests { mod db_mgr { use std::str::FromStr; - use crate::db; + use crate::keys; pub const LIMIT: usize = 6; pub async fn get_pool() -> Result { dotenvy::dotenv().ok(); - let tm_db_url = std::env::var(db::keys::DBURL).expect("DATABASE_URL must be present"); + let tm_db_url = std::env::var(keys::DBURL).expect("DATABASE_URL must be present"); let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap(); sqlx::PgPool::connect_with(tm_options).await } @@ -177,7 +172,7 @@ mod tests { pub async fn connect_to_db(db_name: &str) -> Result { dotenvy::dotenv().ok(); let db_url = - std::env::var(db::keys::DBURL).expect("DATABASE_URL must be set for tests"); + std::env::var(keys::DBURL).expect("DATABASE_URL must be set for tests"); let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name); sqlx::PgPool::connect_with(options).await } @@ -206,7 +201,7 @@ mod tests { pub fn get_database_name() -> Result> { dotenvy::dotenv().ok(); // Load .env file if it exists - match std::env::var(db::keys::DBURL) { + match std::env::var(keys::DBURL) { Ok(database_url) => { let parsed_url = url::Url::parse(&database_url)?; if parsed_url.scheme() == "postgres" || parsed_url.scheme() == "postgresql" { -- 2.47.3 From 81655224b33f5758f3b732b35c4e0db742d5ed2b Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 17:13:56 -0400 Subject: [PATCH 13/28] Moved environement related code to its own module --- src/environment.rs | 5 +++++ src/keys.rs | 1 + src/main.rs | 12 +++++++----- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 src/environment.rs diff --git a/src/environment.rs b/src/environment.rs new file mode 100644 index 0000000..f3487fa --- /dev/null +++ b/src/environment.rs @@ -0,0 +1,5 @@ + +pub async fn get_db_url() -> String { + dotenvy::dotenv().ok(); + std::env::var(crate::keys::DBURL).expect(crate::keys::error::ERROR) +} diff --git a/src/keys.rs b/src/keys.rs index b58a50b..3408552 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,4 +1,5 @@ pub const DBURL: &str = "DATABASE_URL"; +pub const ROOT_DIRECTORY: &str = "ROOT_DIRECTORY"; pub mod error { pub const ERROR: &str = "DATABASE_URL must be set in .env"; diff --git a/src/main.rs b/src/main.rs index e314954..7962388 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ pub mod callers; +pub mod environment; pub mod keys; pub mod db { use sqlx::postgres::PgPoolOptions; - use std::env; use crate::keys; @@ -13,7 +13,7 @@ pub mod db { } pub async fn create_pool() -> Result { - let database_url = get_db_url().await; + let database_url = crate::environment::get_db_url().await; println!("Database url: {:?}", database_url); PgPoolOptions::new() @@ -22,10 +22,12 @@ pub mod db { .await } + /* async fn get_db_url() -> String { dotenvy::dotenv().ok(); env::var(keys::DBURL).expect(keys::error::ERROR) } + */ pub async fn migrations(pool: &sqlx::PgPool) { // Run migrations using the sqlx::migrate! macro @@ -158,7 +160,8 @@ mod tests { pub async fn get_pool() -> Result { dotenvy::dotenv().ok(); - let tm_db_url = std::env::var(keys::DBURL).expect("DATABASE_URL must be present"); + // let tm_db_url = std::env::var(keys::DBURL).expect("DATABASE_URL must be present"); + let tm_db_url = crate::environment::get_db_url().await; let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap(); sqlx::PgPool::connect_with(tm_options).await } @@ -171,8 +174,7 @@ mod tests { pub async fn connect_to_db(db_name: &str) -> Result { dotenvy::dotenv().ok(); - let db_url = - std::env::var(keys::DBURL).expect("DATABASE_URL must be set for tests"); + let db_url = crate::environment::get_db_url().await; let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name); sqlx::PgPool::connect_with(options).await } -- 2.47.3 From 01d227dafd723af1cb44b047bf95f7add41e52e4 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 17:43:09 -0400 Subject: [PATCH 14/28] Added function to get root_directory --- src/environment.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/environment.rs b/src/environment.rs index f3487fa..936efbf 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -3,3 +3,8 @@ pub async fn get_db_url() -> String { dotenvy::dotenv().ok(); std::env::var(crate::keys::DBURL).expect(crate::keys::error::ERROR) } + +pub async fn get_root_directory() -> Result { + dotenvy::dotenv().ok(); + std::env::var(crate::keys::ROOT_DIRECTORY) +} -- 2.47.3 From f0736da861c8b4e557ad55643654031f8e7e05a5 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 17:43:30 -0400 Subject: [PATCH 15/28] Added endpoint to create song --- src/callers/song.rs | 116 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index 98f0100..38a35cc 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -30,6 +30,7 @@ pub mod request { pub duration: i32, pub audio_type: String, pub user_id: uuid::Uuid, + pub song_queue_id: uuid::Uuid, } impl Request { @@ -38,6 +39,31 @@ pub mod request { || !self.album.is_empty() || !self.genre.is_empty() || !self.date.is_empty() || self.track > 0 || self.disc > 0 || self.track_count > 0 || self.disc_count > 0 || self.duration > 0 || !self.audio_type.is_empty() || !self.user_id.is_nil() + || !self.song_queue_id.is_nil() + } + + pub fn to_song(&self) -> icarus_models::song::Song { + icarus_models::song::Song { + id: uuid::Uuid::nil(), + title: self.title.clone(), + artist: self.artist.clone(), + album_artist: self.album_artist.clone(), + album: self.album.clone(), + genre: self.genre.clone(), + year: self.date[..3].parse().unwrap(), + track: self.track.clone(), + disc: self.disc.clone(), + track_count: self.track_count.clone(), + disc_count: self.disc_count.clone(), + duration: self.duration.clone(), + audio_type: self.audio_type.clone(), + user_id: self.user_id.clone(), + // TODO: Change the type of this in icarus_models lib + date_created: String::new(), + filename: String::new(), + data: Vec::new(), + directory: String::new(), + } } } } @@ -88,6 +114,7 @@ pub mod response { #[derive(Default, serde::Deserialize, serde::Serialize)] pub struct Response { pub message: String, + pub data: Vec } } } @@ -104,6 +131,57 @@ pub mod status { } } +mod song { + use sqlx::Row; + + // TODO: Change first parameter of return value from string to a time type + pub async fn insert(pool: &sqlx::PgPool, song: &icarus_models::song::Song) -> Result<(String, 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; + "# + ) + .bind(song.title.clone()) + .bind(song.artist.clone()) + .bind(song.album_artist.clone()) + .bind(song.album.clone()) + .bind(song.genre.clone()) + .bind(song.year) + .bind(song.track) + .bind(song.disc) + .bind(song.track_count) + .bind(song.disc_count) + .bind(song.duration) + .bind(song.audio_type.clone()) + .bind(song.filename.clone()) + .bind(song.directory.clone()) + .bind(song.user_id) + .fetch_one(pool) + .await + .map_err(|e| { + eprintln!("Error inserting query: {:?}", e); + }); + + match result { + Ok(row) => { + let id: uuid::Uuid = row + .try_get("id") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(); + let date_created = row.try_get("date_created").map_err(|_e| sqlx::Error::RowNotFound).unwrap(); + + Ok((date_created, id)) + } + Err(_) => { + Err(sqlx::Error::RowNotFound) + } + } + } +} + mod song_queue { use sqlx::Row; @@ -502,7 +580,43 @@ pub mod endpoint { let mut response = super::response::create_metadata::Response::default(); if payload.is_valid() { - (axum::http::StatusCode::OK, axum::Json(response)) + let mut song = payload.to_song(); + song.filename = song.generate_filename(icarus_models::types::MusicTypes::FlacExtension, true); + song.directory = crate::environment::get_root_directory().await.unwrap(); + match song_queue::get_data(&pool, &payload.song_queue_id).await { + Ok(data) => { + song.data = data; + let dir = std::path::Path::new(&song.directory); + let save_path = dir.join(&song.filename); + let mut file = std::fs::File::create(&save_path).unwrap(); + file.write_all(&song.data).unwrap(); + + match song.song_path() { + Ok(_) => { + match super::song::insert(&pool, &song).await { + Ok((date_created, id)) => { + song.id = id; + song.date_created = date_created; + // response.data.push(returned_song); + (axum::http::StatusCode::OK, axum::Json(response)) + } + Err(err) => { + response.message = err.to_string(); + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) + } + } + } + Err(err) => { + response.message = err.to_string(); + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) + } + } + } + Err(err) => { + response.message = err.to_string(); + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) + } + } } else { response.message = String::from("Request body is not valid"); (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) -- 2.47.3 From 521ad5200e0069b02913ec675b537f4299855dcc Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 18:08:00 -0400 Subject: [PATCH 16/28] Minor fixes --- src/callers/mod.rs | 2 ++ src/callers/song.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/callers/mod.rs b/src/callers/mod.rs index 1dbc6c4..99e918a 100644 --- a/src/callers/mod.rs +++ b/src/callers/mod.rs @@ -11,4 +11,6 @@ pub mod endpoints { pub const QUEUECOVERART: &str = "/api/v2/coverart/queue"; pub const QUEUECOVERARTDATA: &str = "/api/v2/coverart/queue/data"; pub const QUEUECOVERARTLINK: &str = "/api/v2/coverart/queue/link"; + + pub const CREATESONG: &str = "/api/v2/song"; } diff --git a/src/callers/song.rs b/src/callers/song.rs index 38a35cc..3990f0f 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -598,6 +598,7 @@ pub mod endpoint { song.id = id; song.date_created = date_created; // response.data.push(returned_song); + response.data.push(song); (axum::http::StatusCode::OK, axum::Json(response)) } Err(err) => { -- 2.47.3 From 3416f0db16777393d2a5a36f4af9389d764b47cd Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 18:09:04 -0400 Subject: [PATCH 17/28] Added test and refactored tests --- src/main.rs | 72 ++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7962388..682dfc6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -106,6 +106,10 @@ pub mod init { crate::callers::endpoints::QUEUECOVERARTLINK, patch(crate::callers::coverart::endpoint::link), ) + .route( + crate::callers::endpoints::CREATESONG, + post(crate::callers::song::endpoint::create_metadata) + ) } pub async fn app() -> axum::Router { @@ -270,6 +274,19 @@ mod tests { app.clone().oneshot(fetch_req).await } + async fn fetch_metadata_queue_req(app: &axum::Router, id: &uuid::Uuid) -> Result { + let uri = format!("{}?id={}", crate::callers::endpoints::QUEUEMETADATA, id); + + let req = 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(); + + app.clone().oneshot(req).await + } + async fn fetch_queue_data_req( app: &axum::Router, id: &uuid::Uuid, @@ -309,6 +326,18 @@ mod tests { app.clone().oneshot(req).await } + async fn queue_metadata_req(app: &axum::Router, id: &uuid::Uuid) -> Result { + let payload = payload_data(&id).await; + + let req = 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(payload.to_string())).unwrap(); + + app.clone().oneshot(req).await + } + async fn coverart_queue_song_queue_link_req( app: &axum::Router, coverart_id: &uuid::Uuid, @@ -537,19 +566,8 @@ 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 = 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 + match queue_metadata_req(&app, &resp.data[0]).await { Ok(response) => { let resp = @@ -596,19 +614,8 @@ 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 = 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 + match queue_metadata_req(&app, &resp.data[0]).await { Ok(response) => { let resp = @@ -617,19 +624,8 @@ mod tests { 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 + match fetch_metadata_queue_req(&app, &id).await { Ok(response) => { let resp = get_resp_data::< @@ -1196,4 +1192,8 @@ mod tests { let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } + + #[tokio::test] + async fn test_create_song() { + } } -- 2.47.3 From 64ac7da3a03856086135d3ab41e5cfc3ea922d58 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 18:44:43 -0400 Subject: [PATCH 18/28] Ironing out some kinks --- src/callers/song.rs | 46 +++++++++++---------- src/main.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 21 deletions(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index 3990f0f..4a351b5 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -111,7 +111,7 @@ pub mod response { } pub mod create_metadata { - #[derive(Default, serde::Deserialize, serde::Serialize)] + #[derive(Debug, Default, serde::Deserialize, serde::Serialize)] pub struct Response { pub message: String, pub data: Vec @@ -136,33 +136,36 @@ mod song { // TODO: Change first parameter of return value from string to a time type pub async fn insert(pool: &sqlx::PgPool, song: &icarus_models::song::Song) -> Result<(String, uuid::Uuid), sqlx::Error> { + + // $11, $12, $13, $14, $15) RETURNING date_created, id; + 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, user_id) + VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id; "# ) - .bind(song.title.clone()) - .bind(song.artist.clone()) - .bind(song.album_artist.clone()) - .bind(song.album.clone()) - .bind(song.genre.clone()) - .bind(song.year) - .bind(song.track) - .bind(song.disc) - .bind(song.track_count) - .bind(song.disc_count) - .bind(song.duration) - .bind(song.audio_type.clone()) - .bind(song.filename.clone()) - .bind(song.directory.clone()) - .bind(song.user_id) + .bind(&song.title) + .bind(&song.artist) + .bind(&song.album_artist) + .bind(&song.album) + .bind(&song.genre) + .bind(&song.year) + .bind(&song.track) + .bind(&song.disc) + .bind(&song.track_count) + .bind(&song.disc_count) + .bind(&song.duration) + .bind(&song.audio_type) + .bind(&song.filename) + .bind(&song.directory) + .bind(&song.user_id) .fetch_one(pool) .await .map_err(|e| { eprintln!("Error inserting query: {:?}", e); + eprintln!("Year: {:?}", song.year); + eprintln!("Song: {:?}", song); }); match result { @@ -171,7 +174,8 @@ mod song { .try_get("id") .map_err(|_e| sqlx::Error::RowNotFound) .unwrap(); - let date_created = row.try_get("date_created").map_err(|_e| sqlx::Error::RowNotFound).unwrap(); + // let date_created = row.try_get("date_created").map_err(|_e| sqlx::Error::RowNotFound).unwrap(); + let date_created = String::from("2025-01-01"); Ok((date_created, id)) } diff --git a/src/main.rs b/src/main.rs index 682dfc6..f52fc73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1195,5 +1195,103 @@ mod tests { #[tokio::test] async fn test_create_song() { + 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"); + + match queue_metadata_req(&app, &resp.data[0]).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]; + + 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 payload = serde_json::json!({ + "title": "Power of Soul", + "artist": "Jimmi Hendrix", + "album_artist": "Jimmi Hendrix", + "album": "Machine Gun", + "genre": "Psychadelic Rock", + "date": "2016-01-01", + "track": 1, + "disc": 1, + "track_count": 11, + "disc_count": 1, + "duration": 330, + "audio_type": "flac", + "user_id": "d6e159c1-9648-4c85-81e5-52f502ff53e4", + "song_queue_id": id + }); + + match app.clone().oneshot( + axum::http::Request::builder() + .method(axum::http::Method::POST) + .uri(crate::callers::endpoints::CREATESONG) + .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::(response).await; + assert_eq!(false, resp.data.is_empty(), "No songs found, Response {:?}", resp); + } + 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; } } -- 2.47.3 From 3d9b5762cfdf81c49aaa52f3212cd73bb5e6cf65 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 19:14:33 -0400 Subject: [PATCH 19/28] Resolved test issue Turns out I was using the wrong id value --- src/callers/song.rs | 15 +++++++++++---- src/main.rs | 12 +++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index 4a351b5..fc185b2 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -163,9 +163,9 @@ mod song { .fetch_one(pool) .await .map_err(|e| { - eprintln!("Error inserting query: {:?}", e); - eprintln!("Year: {:?}", song.year); - eprintln!("Song: {:?}", song); + eprintln!("Error inserting query: {:?} year {:?} song {:?}", e, song.year, song); + // eprintln!("Year: {:?}", song.year); + // eprintln!("Song: {:?}", song); }); match result { @@ -586,10 +586,15 @@ pub mod endpoint { if payload.is_valid() { let mut song = payload.to_song(); song.filename = song.generate_filename(icarus_models::types::MusicTypes::FlacExtension, true); + eprintln!("File: {:?}", song.filename); song.directory = crate::environment::get_root_directory().await.unwrap(); + eprintln!("Song queue_id: {:?}", payload.song_queue_id); + match song_queue::get_data(&pool, &payload.song_queue_id).await { Ok(data) => { + eprintln!("Year: {:?}", song.year); song.data = data; + eprintln!("Song fetched: {:?}", song); let dir = std::path::Path::new(&song.directory); let save_path = dir.join(&song.filename); let mut file = std::fs::File::create(&save_path).unwrap(); @@ -606,7 +611,8 @@ pub mod endpoint { (axum::http::StatusCode::OK, axum::Json(response)) } Err(err) => { - response.message = err.to_string(); + println!("Song: {:?}", song); + response.message = format!("{:?} song {:?}", err.to_string(), song); (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) } } @@ -618,6 +624,7 @@ pub mod endpoint { } } Err(err) => { + eprintln!("Big whoopsie {:?}", song); response.message = err.to_string(); (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) } diff --git a/src/main.rs b/src/main.rs index f52fc73..d74c8e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,6 @@ pub mod db { use sqlx::postgres::PgPoolOptions; - use crate::keys; - pub mod connection_settings { pub const MAXCONN: u32 = 10; } @@ -390,6 +388,7 @@ mod tests { }) } + /* #[tokio::test] async fn test_song_queue() { let tm_pool = db_mgr::get_pool().await.unwrap(); @@ -1192,6 +1191,7 @@ mod tests { let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } + */ #[tokio::test] async fn test_create_song() { @@ -1238,6 +1238,7 @@ mod tests { >(response) .await; assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); + let song_q_id = resp.data[0].song_queue_id; let payload = serde_json::json!({ @@ -1254,9 +1255,11 @@ mod tests { "duration": 330, "audio_type": "flac", "user_id": "d6e159c1-9648-4c85-81e5-52f502ff53e4", - "song_queue_id": id + "song_queue_id": song_q_id }); + eprintln!("Payload: {:?}", payload); + match app.clone().oneshot( axum::http::Request::builder() .method(axum::http::Method::POST) @@ -1271,6 +1274,9 @@ mod tests { Ok(response) => { let resp = get_resp_data::(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); } Err(err) => { assert!(false, "Error: {:?}", err); -- 2.47.3 From 55ff6800e144f729e6a1e5a6aad1ee5e6881bb00 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 19:31:32 -0400 Subject: [PATCH 20/28] Added env variable in workflow --- .github/workflows/workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index b61eba6..bd4d034 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -66,6 +66,7 @@ jobs: - name: Run tests env: + ROOT_DIRECTORY: "/" DATABASE_URL: "postgres://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}" run: cargo test --verbose -- 2.47.3 From d9ff64ab07299ec1c3d94638ea9b820ae5289e8d Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 19:34:37 -0400 Subject: [PATCH 21/28] Added TODO --- src/callers/song.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/callers/song.rs b/src/callers/song.rs index fc185b2..f206d71 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -408,6 +408,7 @@ pub mod endpoint { data.len() ); + // TODO: Remove this // Save the file to disk let mut file = std::fs::File::create(&file_name).unwrap(); file.write_all(&data).unwrap(); -- 2.47.3 From 503857a87d2349d9f9fce4e559781f1fee8ea3e6 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 19:35:53 -0400 Subject: [PATCH 22/28] Updated env variable --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index bd4d034..4bd1c03 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -66,7 +66,7 @@ jobs: - name: Run tests env: - ROOT_DIRECTORY: "/" + ROOT_DIRECTORY: "/tmp" DATABASE_URL: "postgres://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}" run: cargo test --verbose -- 2.47.3 From 66791e80f608b7ffbc81f042ace5bef3228598e4 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 19:49:56 -0400 Subject: [PATCH 23/28] clippy warning fixes --- src/callers/song.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index f206d71..685b331 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -51,13 +51,13 @@ pub mod request { album: self.album.clone(), genre: self.genre.clone(), year: self.date[..3].parse().unwrap(), - track: self.track.clone(), - disc: self.disc.clone(), - track_count: self.track_count.clone(), - disc_count: self.disc_count.clone(), - duration: self.duration.clone(), + track: self.track, + disc: self.disc, + track_count: self.track_count, + disc_count: self.disc_count, + duration: self.duration, audio_type: self.audio_type.clone(), - user_id: self.user_id.clone(), + user_id: self.user_id, // TODO: Change the type of this in icarus_models lib date_created: String::new(), filename: String::new(), @@ -131,7 +131,7 @@ pub mod status { } } -mod song { +mod song_db { use sqlx::Row; // TODO: Change first parameter of return value from string to a time type @@ -150,16 +150,16 @@ mod song { .bind(&song.album_artist) .bind(&song.album) .bind(&song.genre) - .bind(&song.year) - .bind(&song.track) - .bind(&song.disc) - .bind(&song.track_count) - .bind(&song.disc_count) - .bind(&song.duration) + .bind(song.year) + .bind(song.track) + .bind(song.disc) + .bind(song.track_count) + .bind(song.disc_count) + .bind(song.duration) .bind(&song.audio_type) .bind(&song.filename) .bind(&song.directory) - .bind(&song.user_id) + .bind(song.user_id) .fetch_one(pool) .await .map_err(|e| { @@ -603,7 +603,7 @@ pub mod endpoint { match song.song_path() { Ok(_) => { - match super::song::insert(&pool, &song).await { + match super::song_db::insert(&pool, &song).await { Ok((date_created, id)) => { song.id = id; song.date_created = date_created; -- 2.47.3 From 6e078b91d28e61562d7f4a286fa637c348e45c7b Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 19:56:05 -0400 Subject: [PATCH 24/28] Making sure date_created is populated when returneD --- src/callers/song.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index 685b331..923fe55 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -142,7 +142,7 @@ mod song_db { 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 id; + VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING date_created, id; "# ) .bind(&song.title) @@ -174,8 +174,9 @@ mod song_db { .try_get("id") .map_err(|_e| sqlx::Error::RowNotFound) .unwrap(); - // let date_created = row.try_get("date_created").map_err(|_e| sqlx::Error::RowNotFound).unwrap(); - let date_created = String::from("2025-01-01"); + let date_created_ty: time::OffsetDateTime = row.try_get("date_created").map_err(|_e| sqlx::Error::RowNotFound).unwrap(); + let mut date_created = String::from("2025-01-01"); + date_created = date_created_ty.to_string(); Ok((date_created, id)) } -- 2.47.3 From 56e3b09dda6b25cc04715d1c939f1bf797cc8fa9 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 20:00:32 -0400 Subject: [PATCH 25/28] Code cleanup --- src/callers/song.rs | 22 +++++----------------- src/main.rs | 8 -------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index 923fe55..b91d7ee 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -136,9 +136,6 @@ mod song_db { // TODO: Change first parameter of return value from string to a time type pub async fn insert(pool: &sqlx::PgPool, song: &icarus_models::song::Song) -> Result<(String, uuid::Uuid), sqlx::Error> { - - // $11, $12, $13, $14, $15) RETURNING date_created, id; - 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) @@ -163,9 +160,7 @@ mod song_db { .fetch_one(pool) .await .map_err(|e| { - eprintln!("Error inserting query: {:?} year {:?} song {:?}", e, song.year, song); - // eprintln!("Year: {:?}", song.year); - // eprintln!("Song: {:?}", song); + eprintln!("Error inserting query: {:?}", e); }); match result { @@ -174,9 +169,8 @@ mod song_db { .try_get("id") .map_err(|_e| sqlx::Error::RowNotFound) .unwrap(); - let date_created_ty: time::OffsetDateTime = row.try_get("date_created").map_err(|_e| sqlx::Error::RowNotFound).unwrap(); - let mut date_created = String::from("2025-01-01"); - date_created = date_created_ty.to_string(); + let date_created_time: time::OffsetDateTime = row.try_get("date_created").map_err(|_e| sqlx::Error::RowNotFound).unwrap(); + let date_created = date_created_time.to_string(); Ok((date_created, id)) } @@ -578,7 +572,6 @@ pub mod endpoint { } } - // TODO: Implement pub async fn create_metadata( axum::Extension(pool): axum::Extension, axum::Json(payload): axum::Json, @@ -588,17 +581,14 @@ pub mod endpoint { if payload.is_valid() { let mut song = payload.to_song(); song.filename = song.generate_filename(icarus_models::types::MusicTypes::FlacExtension, true); - eprintln!("File: {:?}", song.filename); song.directory = crate::environment::get_root_directory().await.unwrap(); - eprintln!("Song queue_id: {:?}", payload.song_queue_id); match song_queue::get_data(&pool, &payload.song_queue_id).await { Ok(data) => { - eprintln!("Year: {:?}", song.year); song.data = data; - eprintln!("Song fetched: {:?}", song); let dir = std::path::Path::new(&song.directory); let save_path = dir.join(&song.filename); + let mut file = std::fs::File::create(&save_path).unwrap(); file.write_all(&song.data).unwrap(); @@ -608,12 +598,11 @@ pub mod endpoint { Ok((date_created, id)) => { song.id = id; song.date_created = date_created; - // response.data.push(returned_song); response.data.push(song); + (axum::http::StatusCode::OK, axum::Json(response)) } Err(err) => { - println!("Song: {:?}", song); response.message = format!("{:?} song {:?}", err.to_string(), song); (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) } @@ -626,7 +615,6 @@ pub mod endpoint { } } Err(err) => { - eprintln!("Big whoopsie {:?}", song); response.message = err.to_string(); (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) } diff --git a/src/main.rs b/src/main.rs index d74c8e2..c1e7bda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,13 +20,6 @@ pub mod db { .await } - /* - async fn get_db_url() -> String { - dotenvy::dotenv().ok(); - env::var(keys::DBURL).expect(keys::error::ERROR) - } - */ - pub async fn migrations(pool: &sqlx::PgPool) { // Run migrations using the sqlx::migrate! macro // Assumes your migrations are in a ./migrations folder relative to Cargo.toml @@ -162,7 +155,6 @@ mod tests { pub async fn get_pool() -> Result { dotenvy::dotenv().ok(); - // let tm_db_url = std::env::var(keys::DBURL).expect("DATABASE_URL must be present"); let tm_db_url = crate::environment::get_db_url().await; let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap(); sqlx::PgPool::connect_with(tm_options).await -- 2.47.3 From b0273d623951cff43b0a1d82641e39e04d83bed5 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 20:01:35 -0400 Subject: [PATCH 26/28] Added TODOs --- src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.rs b/src/main.rs index c1e7bda..12d22d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,18 +117,23 @@ pub mod init { } } +// TODO: Move elsewhere fn get_full() -> String { get_address() + ":" + &get_port() } +// TODO: Move elsewhere fn get_address() -> String { String::from("0.0.0.0") } +// TODO: Move elsewhere fn get_port() -> String { String::from("3000") } +// TODO: Move elsewhere pub const ROOT: &str = "/"; +// TODO: Move elsewhere // basic handler that responds with a static string pub async fn root() -> &'static str { "Hello, World!" -- 2.47.3 From 7e818154ffa9830e8e7e10293cabcf675f5712d8 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 20:02:27 -0400 Subject: [PATCH 27/28] Uncommenting tests --- src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 12d22d6..15b7ac2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -385,7 +385,6 @@ mod tests { }) } - /* #[tokio::test] async fn test_song_queue() { let tm_pool = db_mgr::get_pool().await.unwrap(); @@ -1188,7 +1187,6 @@ mod tests { let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } - */ #[tokio::test] async fn test_create_song() { -- 2.47.3 From add0738da5cd8992236b0fb138b7467d22e5b9cf Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Fri, 23 May 2025 20:04:11 -0400 Subject: [PATCH 28/28] Code formatting --- src/callers/song.rs | 65 +++++++++++++++++++++-------------- src/environment.rs | 1 - src/main.rs | 83 ++++++++++++++++++++++++++------------------- 3 files changed, 89 insertions(+), 60 deletions(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index b91d7ee..66731c2 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -35,10 +35,19 @@ pub mod request { impl Request { pub fn is_valid(&self) -> bool { - !self.title.is_empty() || !self.artist.is_empty() || !self.album_artist.is_empty() - || !self.album.is_empty() || !self.genre.is_empty() || !self.date.is_empty() - || self.track > 0 || self.disc > 0 || self.track_count > 0 || self.disc_count > 0 - || self.duration > 0 || !self.audio_type.is_empty() || !self.user_id.is_nil() + !self.title.is_empty() + || !self.artist.is_empty() + || !self.album_artist.is_empty() + || !self.album.is_empty() + || !self.genre.is_empty() + || !self.date.is_empty() + || self.track > 0 + || self.disc > 0 + || self.track_count > 0 + || self.disc_count > 0 + || self.duration > 0 + || !self.audio_type.is_empty() + || !self.user_id.is_nil() || !self.song_queue_id.is_nil() } @@ -114,7 +123,7 @@ pub mod response { #[derive(Debug, Default, serde::Deserialize, serde::Serialize)] pub struct Response { pub message: String, - pub data: Vec + pub data: Vec, } } } @@ -135,7 +144,10 @@ mod song_db { use sqlx::Row; // TODO: Change first parameter of return value from string to a time type - pub async fn insert(pool: &sqlx::PgPool, song: &icarus_models::song::Song) -> Result<(String, uuid::Uuid), sqlx::Error> { + pub async fn insert( + pool: &sqlx::PgPool, + song: &icarus_models::song::Song, + ) -> Result<(String, 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) @@ -169,14 +181,15 @@ mod song_db { .try_get("id") .map_err(|_e| sqlx::Error::RowNotFound) .unwrap(); - let date_created_time: time::OffsetDateTime = row.try_get("date_created").map_err(|_e| sqlx::Error::RowNotFound).unwrap(); + let date_created_time: time::OffsetDateTime = row + .try_get("date_created") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(); let date_created = date_created_time.to_string(); Ok((date_created, id)) } - Err(_) => { - Err(sqlx::Error::RowNotFound) - } + Err(_) => Err(sqlx::Error::RowNotFound), } } } @@ -575,12 +588,16 @@ pub mod endpoint { pub async fn create_metadata( axum::Extension(pool): axum::Extension, axum::Json(payload): axum::Json, - ) -> (axum::http::StatusCode, axum::Json) { + ) -> ( + axum::http::StatusCode, + axum::Json, + ) { let mut response = super::response::create_metadata::Response::default(); if payload.is_valid() { let mut song = payload.to_song(); - song.filename = song.generate_filename(icarus_models::types::MusicTypes::FlacExtension, true); + song.filename = + song.generate_filename(icarus_models::types::MusicTypes::FlacExtension, true); song.directory = crate::environment::get_root_directory().await.unwrap(); match song_queue::get_data(&pool, &payload.song_queue_id).await { @@ -593,21 +610,19 @@ pub mod endpoint { file.write_all(&song.data).unwrap(); match song.song_path() { - Ok(_) => { - match super::song_db::insert(&pool, &song).await { - Ok((date_created, id)) => { - song.id = id; - song.date_created = date_created; - response.data.push(song); + Ok(_) => match super::song_db::insert(&pool, &song).await { + Ok((date_created, id)) => { + song.id = id; + song.date_created = date_created; + 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)) - } + (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::BAD_REQUEST, axum::Json(response)) diff --git a/src/environment.rs b/src/environment.rs index 936efbf..27446ff 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -1,4 +1,3 @@ - pub async fn get_db_url() -> String { dotenvy::dotenv().ok(); std::env::var(crate::keys::DBURL).expect(crate::keys::error::ERROR) diff --git a/src/main.rs b/src/main.rs index 15b7ac2..d9d4892 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ pub mod keys; pub mod db { use sqlx::postgres::PgPoolOptions; - + pub mod connection_settings { pub const MAXCONN: u32 = 10; } @@ -99,7 +99,7 @@ pub mod init { ) .route( crate::callers::endpoints::CREATESONG, - post(crate::callers::song::endpoint::create_metadata) + post(crate::callers::song::endpoint::create_metadata), ) } @@ -269,15 +269,18 @@ mod tests { app.clone().oneshot(fetch_req).await } - async fn fetch_metadata_queue_req(app: &axum::Router, id: &uuid::Uuid) -> Result { + async fn fetch_metadata_queue_req( + app: &axum::Router, + id: &uuid::Uuid, + ) -> Result { let uri = format!("{}?id={}", crate::callers::endpoints::QUEUEMETADATA, id); let req = 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(); + .method(axum::http::Method::GET) + .uri(uri) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(axum::body::Body::empty()) + .unwrap(); app.clone().oneshot(req).await } @@ -321,14 +324,18 @@ mod tests { app.clone().oneshot(req).await } - async fn queue_metadata_req(app: &axum::Router, id: &uuid::Uuid) -> Result { + async fn queue_metadata_req( + app: &axum::Router, + id: &uuid::Uuid, + ) -> Result { let payload = payload_data(&id).await; let req = 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(payload.to_string())).unwrap(); + .body(axum::body::Body::from(payload.to_string())) + .unwrap(); app.clone().oneshot(req).await } @@ -562,8 +569,7 @@ mod tests { 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 - { + match queue_metadata_req(&app, &resp.data[0]).await { Ok(response) => { let resp = get_resp_data::(response) @@ -610,8 +616,7 @@ mod tests { 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 - { + match queue_metadata_req(&app, &resp.data[0]).await { Ok(response) => { let resp = get_resp_data::(response) @@ -620,8 +625,7 @@ mod tests { let id = resp.data[0]; - match fetch_metadata_queue_req(&app, &id).await - { + match fetch_metadata_queue_req(&app, &id).await { Ok(response) => { let resp = get_resp_data::< crate::callers::metadata::response::fetch_metadata::Response, @@ -1215,8 +1219,7 @@ mod tests { 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 - { + match queue_metadata_req(&app, &resp.data[0]).await { Ok(response) => { let resp = get_resp_data::(response) @@ -1225,8 +1228,7 @@ mod tests { let id = resp.data[0]; - match fetch_metadata_queue_req(&app, &id).await - { + match fetch_metadata_queue_req(&app, &id).await { Ok(response) => { let resp = get_resp_data::< crate::callers::metadata::response::fetch_metadata::Response, @@ -1235,7 +1237,6 @@ mod tests { assert_eq!(false, resp.data.is_empty(), "Data should not be empty"); let song_q_id = resp.data[0].song_queue_id; - let payload = serde_json::json!({ "title": "Power of Soul", "artist": "Jimmi Hendrix", @@ -1255,23 +1256,37 @@ mod tests { eprintln!("Payload: {:?}", payload); - match app.clone().oneshot( - axum::http::Request::builder() - .method(axum::http::Method::POST) - .uri(crate::callers::endpoints::CREATESONG) - .header( - axum::http::header::CONTENT_TYPE, - "application/json" - ) - .body(axum::body::Body::from(payload.to_string())) - .unwrap() - ).await { + match app + .clone() + .oneshot( + axum::http::Request::builder() + .method(axum::http::Method::POST) + .uri(crate::callers::endpoints::CREATESONG) + .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::(response).await; - assert_eq!(false, resp.data.is_empty(), "No songs found, Response {:?}", resp); + 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); + assert_eq!( + false, + song_id.is_nil(), + "Song id should not be nil {:?}", + song + ); } Err(err) => { assert!(false, "Error: {:?}", err); -- 2.47.3