From 08324431a158ee474572033edeaeac8416b49b33 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 20 Apr 2025 20:21:21 -0400 Subject: [PATCH] Added code --- src/callers/song.rs | 34 +++++---- src/main.rs | 176 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 179 insertions(+), 31 deletions(-) diff --git a/src/callers/song.rs b/src/callers/song.rs index 59a5b28..ce9c174 100644 --- a/src/callers/song.rs +++ b/src/callers/song.rs @@ -5,7 +5,6 @@ pub mod request { pub struct Request { pub message: String, } - } pub mod response { @@ -26,12 +25,15 @@ mod song_queue { pub id: uuid::Uuid, } - pub async fn insert(pool: &sqlx::PgPool, data: &Vec, filename: &String) -> Result { - + pub async fn insert( + pool: &sqlx::PgPool, + data: &Vec, + filename: &String, + ) -> Result { let result = sqlx::query( r#" INSERT INTO "songQueue" (data, filename) VALUES($1, $2) RETURNING id; - "# + "#, ) .bind(&data) .bind(&filename) @@ -43,17 +45,19 @@ mod song_queue { match result { Ok(row) => { - let id: uuid::Uuid = row.try_get("id") - .map_err(|_e| sqlx::Error::RowNotFound).unwrap(); + let id: uuid::Uuid = row + .try_get("id") + .map_err(|_e| sqlx::Error::RowNotFound) + .unwrap(); Ok(id) } - Err(_err) => Err(sqlx::Error::RowNotFound) + Err(_err) => Err(sqlx::Error::RowNotFound), } } } pub mod endpoint { - use axum::{http::{StatusCode}, Json}; + use axum::{Json, http::StatusCode}; // use axum::extract::M use std::io::Write; @@ -71,9 +75,12 @@ pub mod endpoint { let name = field.name().unwrap().to_string(); let file_name = field.file_name().unwrap().to_string(); let content_type = field.content_type().unwrap().to_string(); - println!("Name {} filename {} content type {}", name, file_name, content_type); + println!( + "Name {} filename {} content type {}", + name, file_name, content_type + ); let data = field.bytes().await.unwrap(); - + println!( "Received file '{}' (name = '{}', content-type = '{}', size = {})", file_name, @@ -81,13 +88,15 @@ pub mod endpoint { content_type, data.len() ); - + // Save the file to disk let mut file = std::fs::File::create(&file_name).unwrap(); file.write_all(&data).unwrap(); let raw_data: Vec = data.to_vec(); - let queue_repo = song_queue::insert(&pool, &raw_data, &file_name).await.unwrap(); + let queue_repo = song_queue::insert(&pool, &raw_data, &file_name) + .await + .unwrap(); results.push(queue_repo); } @@ -95,5 +104,4 @@ pub mod endpoint { (StatusCode::OK, Json(response)) } - } diff --git a/src/main.rs b/src/main.rs index 02bbd4b..e30b9bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,8 @@ -use axum::{ - // Json, - Router, - // http::StatusCode, - // routing::{get, post}, - routing::{get, post}, -}; -use tower_http::timeout::TimeoutLayer; -use std::time::Duration; // use serde::{Deserialize, Serialize}; pub mod callers; -mod db { +pub mod db { use sqlx::postgres::PgPoolOptions; use std::env; @@ -65,13 +56,8 @@ async fn main() { db::migrations(&pool).await; // build our application with a route - let app = Router::new() - // `GET /` goes to `root` - .route("/", get(root)) - .route(callers::endpoints::QUEUESONG, post(callers::song::endpoint::queue_song)) - .layer(axum::Extension(pool)) - .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) - .layer(TimeoutLayer::new(Duration::from_secs(300))); + let app = init::app().await; + // `GET /` goes to `root` // `POST /users` goes to `create_user` // .route("/users", post(create_user)); @@ -80,6 +66,35 @@ async fn main() { axum::serve(listener, app).await.unwrap(); } +pub mod init { + use axum::routing::{get, post}; + use std::time::Duration; + use tower_http::timeout::TimeoutLayer; + + pub async fn routes() -> axum::Router { + axum::Router::new() + // `GET /` goes to `root` + .route(crate::ROOT, get(crate::root)) + .route( + crate::callers::endpoints::QUEUESONG, + post(crate::callers::song::endpoint::queue_song), + ) + } + + pub async fn app() -> axum::Router { + let pool = crate::db::create_pool() + .await + .expect("Failed to create pool"); + crate::db::migrations(&pool).await; + + routes() + .await + .layer(axum::Extension(pool)) + .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) + .layer(TimeoutLayer::new(Duration::from_secs(300))) + } +} + fn get_full() -> String { get_address() + ":" + &get_port() } @@ -91,7 +106,132 @@ fn get_port() -> String { String::from("3000") } +pub const ROOT: &str = "/"; // basic handler that responds with a static string -async fn root() -> &'static str { +pub async fn root() -> &'static str { "Hello, World!" } + +#[cfg(test)] +mod tests { + use crate::db; + + use tower::ServiceExt; + + mod db_mgr { + use std::str::FromStr; + + use crate::db; + + 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_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap(); + sqlx::PgPool::connect_with(tm_options).await + } + + pub async fn generate_db_name() -> String { + let db_name = + get_database_name().unwrap() + &"_" + &uuid::Uuid::new_v4().to_string()[..LIMIT]; + db_name + } + + 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"); + let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name); + sqlx::PgPool::connect_with(options).await + } + + pub async fn create_database( + template_pool: &sqlx::PgPool, + db_name: &str, + ) -> Result<(), sqlx::Error> { + let create_query = format!("CREATE DATABASE {}", db_name); + match sqlx::query(&create_query).execute(template_pool).await { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + + // Function to drop a database + pub async fn drop_database( + template_pool: &sqlx::PgPool, + db_name: &str, + ) -> Result<(), sqlx::Error> { + let drop_query = format!("DROP DATABASE IF EXISTS {} WITH (FORCE)", db_name); + sqlx::query(&drop_query).execute(template_pool).await?; + Ok(()) + } + + pub fn get_database_name() -> Result> { + dotenvy::dotenv().ok(); // Load .env file if it exists + + match std::env::var(db::keys::DBURL) { + Ok(database_url) => { + let parsed_url = url::Url::parse(&database_url)?; + if parsed_url.scheme() == "postgres" || parsed_url.scheme() == "postgresql" { + match parsed_url + .path_segments() + .and_then(|segments| segments.last().map(|s| s.to_string())) + { + Some(sss) => Ok(sss), + None => Err("Error parsing".into()), + } + } else { + // Handle other database types if needed + Err("Error parsing".into()) + } + } + Err(_) => { + // DATABASE_URL environment variable not found + Err("Error parsing".into()) + } + } + } + } + + #[tokio::test] + async fn test_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 = crate::init::routes().await.layer(axum::Extension(pool)); + + /* + let response = app + .clone() + .oneshot( + axum::http::Request::builder() + .method(axum::http::Method::POST) + .header( + axum::http::header::CONTENT_TYPE, + format!( + "multipart/form-data; boundary={}", + multipart_body.boundary() + ), + ) + .uri(crate::callers::endpoints::QUEUESONG) + .body(axum::body::Body::from(multipart_bytes)), + ) + .await; + */ + + let _ = db_mgr::drop_database(&tm_pool, &db_name).await; + } +}