From f29c876ad6cf58bc5656134d7071448224074569 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 14:52:11 -0400 Subject: [PATCH 01/42] Saving changes --- src/config/mod.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 11 ++++ 3 files changed, 137 insertions(+) create mode 100644 src/config/mod.rs create mode 100644 src/lib.rs diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..d51b480 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,125 @@ + +pub mod host { + pub const PORT: i64 = 9081; + + pub fn get_full() -> String { + String::new() + } +} + +pub mod init { + use std::time::Duration; + +use axum::routing::{delete, get, patch, post}; +use tower_http::timeout::TimeoutLayer; +use utoipa::OpenApi; + +use crate::callers::coverart as coverart_caller; +use crate::callers::queue::coverart as coverart_queue_callers; +use crate::callers::queue::metadata as metadata_queue_caller; +use crate::callers::queue::song as song_queue_callers; +use crate::callers::song as song_caller; +use coverart_caller::endpoint as coverart_endpoints; +use coverart_caller::response as coverart_responses; +use coverart_queue_callers::endpoint as coverart_queue_endpoints; +use coverart_queue_callers::response as coverart_queue_responses; +use metadata_queue_caller::endpoint as metadata_queue_endpoints; +use metadata_queue_caller::response as metadata_queue_responses; +use song_caller::endpoint as song_endpoints; +use song_caller::response as song_responses; +use song_queue_callers::endpoint as song_queue_endpoints; +use song_queue_callers::response as song_queue_responses; + +mod cors { + pub async fn configure_cors() -> tower_http::cors::CorsLayer { + let cors = tower_http::cors::CorsLayer::new() + .allow_methods([ + axum::http::Method::GET, + axum::http::Method::POST, + axum::http::Method::PUT, + axum::http::Method::DELETE, + ]) // Specify allowed methods:cite[2] + .allow_headers([ + axum::http::header::CONTENT_TYPE, + axum::http::header::AUTHORIZATION, + ]) // Specify allowed headers:cite[2] + .allow_credentials(true) // If you need to send cookies or authentication headers:cite[2] + .max_age(std::time::Duration::from_secs(3600)); // Cache the preflight response for 1 hour:cite[2] + + // Dynamically set the allowed origin based on the environment + match std::env::var(textsender_models::envy::keys::APP_ENV).as_deref() { + Ok("production") => { + // In production, allow only your specific, trusted origins + let allowed_origins_env = textsender_models::envy::environment::get_allowed_origins().await; + match textsender_models::envy::utility::delimitize(&allowed_origins_env) { + Ok(alwd) => { + let allowed_origins: Vec = alwd + .into_iter() + .map(|a| a.parse::().unwrap()) + .collect(); + cors.allow_origin(allowed_origins) + } + Err(err) => { + eprintln!("Error getting allowed origins: Error: {err:?}"); + std::process::exit(-1); + } + } + } + _ => { + // Development (default): Allow localhost origins + cors.allow_origin(vec![ + "http://localhost:8000".parse().unwrap(), + "http://127.0.0.1:8000".parse().unwrap(), + "http://localhost:4200".parse().unwrap(), + "http://127.0.0.1:4200".parse().unwrap(), + ]) + } + } + } +} + +#[derive(utoipa::OpenApi)] +#[openapi( + paths(song_queue_endpoints::queue_song,), + components(schemas(song_queue_callers::response::song_queue::Response)), + tags( + (name = "textsender API", description = "Web API to manage texting") + ) +)] +struct ApiDoc; + +pub async fn routes() -> axum::Router { + axum::Router::new() + .route( + crate::callers::queue::endpoints::QUEUESONG, + post(crate::callers::queue::song::endpoint::queue_song).route_layer( + axum::middleware::from_fn(crate::auth::auth::), + ), + ) + .layer(cors::configure_cors().await) +} + +pub async fn app() -> axum::Router { + let pool = crate::db::create_pool() + .await + .expect("Failed to create pool"); + + crate::db::migrations(&pool).await; + + let cors = cors::configure_cors().await; + + routes() + .await + .merge( + utoipa_swagger_ui::SwaggerUi::new("/swagger-ui") + .url("/api-docs/openapi.json", ApiDoc::openapi()), + ) + .layer(axum::Extension(pool)) + .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) + .layer(TimeoutLayer::with_status_code( + axum::http::StatusCode::OK, + Duration::from_secs(300), + )) + .layer(cors) +} +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ef68c36 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod config; diff --git a/src/main.rs b/src/main.rs index 47a4039..d499e1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,4 +2,15 @@ async fn main() { // initialize tracing tracing_subscriber::fmt::init(); + + match tokio::net::TcpListener::bind(textsender_api::config::host::get_full()).await { + Ok(listener) => { + // build our application with routes + let app = textsender_api::config::init::app().await; + axum::serve(listener, app).await.unwrap(); + } + Err(err) => { + eprintln!("Error: {err:?}") + } + } } -- 2.47.3 From 93ab1d5db0280dd1120b7133853293e8685f0cae Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 14:52:34 -0400 Subject: [PATCH 02/42] Saving changes --- src/config/mod.rs | 194 +++++++++++++++++++++++----------------------- src/main.rs | 2 +- 2 files changed, 98 insertions(+), 98 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index d51b480..a3383b6 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,3 @@ - pub mod host { pub const PORT: i64 = 9081; @@ -10,116 +9,117 @@ pub mod host { pub mod init { use std::time::Duration; -use axum::routing::{delete, get, patch, post}; -use tower_http::timeout::TimeoutLayer; -use utoipa::OpenApi; + use axum::routing::{delete, get, patch, post}; + use tower_http::timeout::TimeoutLayer; + use utoipa::OpenApi; -use crate::callers::coverart as coverart_caller; -use crate::callers::queue::coverart as coverart_queue_callers; -use crate::callers::queue::metadata as metadata_queue_caller; -use crate::callers::queue::song as song_queue_callers; -use crate::callers::song as song_caller; -use coverart_caller::endpoint as coverart_endpoints; -use coverart_caller::response as coverart_responses; -use coverart_queue_callers::endpoint as coverart_queue_endpoints; -use coverart_queue_callers::response as coverart_queue_responses; -use metadata_queue_caller::endpoint as metadata_queue_endpoints; -use metadata_queue_caller::response as metadata_queue_responses; -use song_caller::endpoint as song_endpoints; -use song_caller::response as song_responses; -use song_queue_callers::endpoint as song_queue_endpoints; -use song_queue_callers::response as song_queue_responses; + use crate::callers::coverart as coverart_caller; + use crate::callers::queue::coverart as coverart_queue_callers; + use crate::callers::queue::metadata as metadata_queue_caller; + use crate::callers::queue::song as song_queue_callers; + use crate::callers::song as song_caller; + use coverart_caller::endpoint as coverart_endpoints; + use coverart_caller::response as coverart_responses; + use coverart_queue_callers::endpoint as coverart_queue_endpoints; + use coverart_queue_callers::response as coverart_queue_responses; + use metadata_queue_caller::endpoint as metadata_queue_endpoints; + use metadata_queue_caller::response as metadata_queue_responses; + use song_caller::endpoint as song_endpoints; + use song_caller::response as song_responses; + use song_queue_callers::endpoint as song_queue_endpoints; + use song_queue_callers::response as song_queue_responses; -mod cors { - pub async fn configure_cors() -> tower_http::cors::CorsLayer { - let cors = tower_http::cors::CorsLayer::new() - .allow_methods([ - axum::http::Method::GET, - axum::http::Method::POST, - axum::http::Method::PUT, - axum::http::Method::DELETE, - ]) // Specify allowed methods:cite[2] - .allow_headers([ - axum::http::header::CONTENT_TYPE, - axum::http::header::AUTHORIZATION, - ]) // Specify allowed headers:cite[2] - .allow_credentials(true) // If you need to send cookies or authentication headers:cite[2] - .max_age(std::time::Duration::from_secs(3600)); // Cache the preflight response for 1 hour:cite[2] + mod cors { + pub async fn configure_cors() -> tower_http::cors::CorsLayer { + let cors = tower_http::cors::CorsLayer::new() + .allow_methods([ + axum::http::Method::GET, + axum::http::Method::POST, + axum::http::Method::PUT, + axum::http::Method::DELETE, + ]) // Specify allowed methods:cite[2] + .allow_headers([ + axum::http::header::CONTENT_TYPE, + axum::http::header::AUTHORIZATION, + ]) // Specify allowed headers:cite[2] + .allow_credentials(true) // If you need to send cookies or authentication headers:cite[2] + .max_age(std::time::Duration::from_secs(3600)); // Cache the preflight response for 1 hour:cite[2] - // Dynamically set the allowed origin based on the environment - match std::env::var(textsender_models::envy::keys::APP_ENV).as_deref() { - Ok("production") => { - // In production, allow only your specific, trusted origins - let allowed_origins_env = textsender_models::envy::environment::get_allowed_origins().await; - match textsender_models::envy::utility::delimitize(&allowed_origins_env) { - Ok(alwd) => { - let allowed_origins: Vec = alwd - .into_iter() - .map(|a| a.parse::().unwrap()) - .collect(); - cors.allow_origin(allowed_origins) - } - Err(err) => { - eprintln!("Error getting allowed origins: Error: {err:?}"); - std::process::exit(-1); + // Dynamically set the allowed origin based on the environment + match std::env::var(textsender_models::envy::keys::APP_ENV).as_deref() { + Ok("production") => { + // In production, allow only your specific, trusted origins + let allowed_origins_env = + textsender_models::envy::environment::get_allowed_origins().await; + match textsender_models::envy::utility::delimitize(&allowed_origins_env) { + Ok(alwd) => { + let allowed_origins: Vec = alwd + .into_iter() + .map(|a| a.parse::().unwrap()) + .collect(); + cors.allow_origin(allowed_origins) + } + Err(err) => { + eprintln!("Error getting allowed origins: Error: {err:?}"); + std::process::exit(-1); + } } } - } - _ => { - // Development (default): Allow localhost origins - cors.allow_origin(vec![ - "http://localhost:8000".parse().unwrap(), - "http://127.0.0.1:8000".parse().unwrap(), - "http://localhost:4200".parse().unwrap(), - "http://127.0.0.1:4200".parse().unwrap(), - ]) + _ => { + // Development (default): Allow localhost origins + cors.allow_origin(vec![ + "http://localhost:8000".parse().unwrap(), + "http://127.0.0.1:8000".parse().unwrap(), + "http://localhost:4200".parse().unwrap(), + "http://127.0.0.1:4200".parse().unwrap(), + ]) + } } } } -} -#[derive(utoipa::OpenApi)] -#[openapi( + #[derive(utoipa::OpenApi)] + #[openapi( paths(song_queue_endpoints::queue_song,), components(schemas(song_queue_callers::response::song_queue::Response)), tags( (name = "textsender API", description = "Web API to manage texting") ) )] -struct ApiDoc; + struct ApiDoc; -pub async fn routes() -> axum::Router { - axum::Router::new() - .route( - crate::callers::queue::endpoints::QUEUESONG, - post(crate::callers::queue::song::endpoint::queue_song).route_layer( - axum::middleware::from_fn(crate::auth::auth::), - ), - ) - .layer(cors::configure_cors().await) -} - -pub async fn app() -> axum::Router { - let pool = crate::db::create_pool() - .await - .expect("Failed to create pool"); - - crate::db::migrations(&pool).await; - - let cors = cors::configure_cors().await; - - routes() - .await - .merge( - utoipa_swagger_ui::SwaggerUi::new("/swagger-ui") - .url("/api-docs/openapi.json", ApiDoc::openapi()), - ) - .layer(axum::Extension(pool)) - .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) - .layer(TimeoutLayer::with_status_code( - axum::http::StatusCode::OK, - Duration::from_secs(300), - )) - .layer(cors) -} + pub async fn routes() -> axum::Router { + axum::Router::new() + .route( + crate::callers::queue::endpoints::QUEUESONG, + post(crate::callers::queue::song::endpoint::queue_song).route_layer( + axum::middleware::from_fn(crate::auth::auth::), + ), + ) + .layer(cors::configure_cors().await) + } + + pub async fn app() -> axum::Router { + let pool = crate::db::create_pool() + .await + .expect("Failed to create pool"); + + crate::db::migrations(&pool).await; + + let cors = cors::configure_cors().await; + + routes() + .await + .merge( + utoipa_swagger_ui::SwaggerUi::new("/swagger-ui") + .url("/api-docs/openapi.json", ApiDoc::openapi()), + ) + .layer(axum::Extension(pool)) + .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) + .layer(TimeoutLayer::with_status_code( + axum::http::StatusCode::OK, + Duration::from_secs(300), + )) + .layer(cors) + } } diff --git a/src/main.rs b/src/main.rs index d499e1d..a6e5f5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ async fn main() { // initialize tracing tracing_subscriber::fmt::init(); - match tokio::net::TcpListener::bind(textsender_api::config::host::get_full()).await { + match tokio::net::TcpListener::bind(textsender_api::config::host::get_full()).await { Ok(listener) => { // build our application with routes let app = textsender_api::config::init::app().await; -- 2.47.3 From 3cf8dcda94daeb7fafa90e2c998608043fe7d385 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 14:56:06 -0400 Subject: [PATCH 03/42] config changes --- src/config/mod.rs | 3 ++- src/lib.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index a3383b6..a768a75 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,7 +2,8 @@ pub mod host { pub const PORT: i64 = 9081; pub fn get_full() -> String { - String::new() + let address: &str = "0.0.0.0"; + format!("{}:{}", address, PORT) } } diff --git a/src/lib.rs b/src/lib.rs index ef68c36..7cb3263 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ pub mod config; +pub mod db; -- 2.47.3 From 36922d3650780469acfc936f683a681b90192c99 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 14:57:53 -0400 Subject: [PATCH 04/42] Adding repo module --- src/lib.rs | 1 + src/repo/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/repo/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 7cb3263..8cb5a3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod config; pub mod db; +pub mod repo; diff --git a/src/repo/mod.rs b/src/repo/mod.rs new file mode 100644 index 0000000..6998909 --- /dev/null +++ b/src/repo/mod.rs @@ -0,0 +1,48 @@ +pub async fn insert( + pool: &sqlx::PgPool, + song: &icarus_models::song::Song, +) -> Result<(time::OffsetDateTime, 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) + .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}"); + }); + + match result { + Ok(row) => { + let id: uuid::Uuid = row + .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 = date_created_time; + + Ok((date_created, id)) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } +} -- 2.47.3 From ba123c8188e07ada67922ed6b43075909e7d8619 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 14:58:21 -0400 Subject: [PATCH 05/42] Adding caller module --- src/caller/mod.rs | 0 src/lib.rs | 1 + 2 files changed, 1 insertion(+) create mode 100644 src/caller/mod.rs diff --git a/src/caller/mod.rs b/src/caller/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 8cb5a3c..dbc25b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod caller; pub mod config; pub mod db; pub mod repo; -- 2.47.3 From ac250bb03e90a4d489e27b6fcd273c65de97f9cb Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 14:58:34 -0400 Subject: [PATCH 06/42] Adding db module --- src/db/mod.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/db/mod.rs diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..b9c7034 --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,24 @@ +use sqlx::postgres::PgPoolOptions; + +pub mod connection_settings { + pub const MAXCONN: u32 = 10; +} + +pub async fn create_pool() -> Result { + let database_url = textsender_models::envy::environment::get_db_url().await.value; + println!("Database url: {database_url}"); + + PgPoolOptions::new() + .max_connections(connection_settings::MAXCONN) + .connect(&database_url) + .await +} + +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 + sqlx::migrate!("./migrations") + .run(pool) + .await + .expect("Failed to run migrations"); +} -- 2.47.3 From cf1a63743221da07a80c8225e50d8a3183a882b0 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:05:47 -0400 Subject: [PATCH 07/42] Saving changes --- src/caller/mod.rs | 1 + src/db/mod.rs | 4 ++- src/repo/mod.rs | 69 ++++++++++++++++++----------------------------- 3 files changed, 30 insertions(+), 44 deletions(-) diff --git a/src/caller/mod.rs b/src/caller/mod.rs index e69de29..8b13789 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -0,0 +1 @@ + diff --git a/src/db/mod.rs b/src/db/mod.rs index b9c7034..d6fe47d 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -5,7 +5,9 @@ pub mod connection_settings { } pub async fn create_pool() -> Result { - let database_url = textsender_models::envy::environment::get_db_url().await.value; + let database_url = textsender_models::envy::environment::get_db_url() + .await + .value; println!("Database url: {database_url}"); PgPoolOptions::new() diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 6998909..fab9efc 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,48 +1,31 @@ -pub async fn insert( - pool: &sqlx::PgPool, - song: &icarus_models::song::Song, -) -> Result<(time::OffsetDateTime, 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; - "# +pub mod contact { + pub async fn insert( + pool: &sqlx::PgPool, + contact: &textsender_models::contact::Contact, + ) -> Result<(), sqlx::Error> { + let result = sqlx::query( + r#" + INSERT INTO "contacts" (firstname, lastname, nickname, phone_number, user_id) + VALUES($1, $2, $3, $4, $5); + "#, ) - .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}"); - }); + .bind(&contact.firstname) + .bind(&contact.lastname) + .bind(&contact.nickname) + .bind(&contact.phone_number) + .bind(&contact.user_id) + .execute(pool) + .await; - match result { - Ok(row) => { - let id: uuid::Uuid = row - .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 = date_created_time; - - Ok((date_created, id)) + match result { + Ok(row) => { + if row.rows_affected() > 0 { + Ok(()) + } else { + Err(sqlx::Error::RowNotFound) + } + } + Err(_) => Err(sqlx::Error::RowNotFound), } - Err(_) => Err(sqlx::Error::RowNotFound), } } -- 2.47.3 From 7de08b0688fe16c797ce05cba02f4cc0890fd9d0 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:07:59 -0400 Subject: [PATCH 08/42] Code changes --- src/caller/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/caller/mod.rs b/src/caller/mod.rs index 8b13789..5dd76e5 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -1 +1,16 @@ +pub mod endpoints { + pub const ROOT: &str = "/"; + pub const ADD_CONTACT: &str = "/api/v1/contact/new"; +} + +pub mod response { + pub const SUCCESSFUL: &str = "SUCCESSFUL"; +} + +/// Basic handler that responds with a static string +pub async fn root() -> &'static str { + "Hello, World!" +} + +pub mod contact {} -- 2.47.3 From 590af6ff10a7e055d77401efaa00b13c578c7472 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:08:36 -0400 Subject: [PATCH 09/42] Adding auth module --- src/auth/mod.rs | 0 src/lib.rs | 1 + 2 files changed, 1 insertion(+) create mode 100644 src/auth/mod.rs diff --git a/src/auth/mod.rs b/src/auth/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index dbc25b9..09f6e1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod auth; pub mod caller; pub mod config; pub mod db; -- 2.47.3 From 6a45bae1a9d6ddc0ce2ff43196b90bace3944397 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:10:40 -0400 Subject: [PATCH 10/42] Adding auth code --- src/auth/mod.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/auth/mod.rs b/src/auth/mod.rs index e69de29..49be85a 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -0,0 +1,61 @@ +use axum::{ + Json, + http::{Request, StatusCode}, + middleware::Next, + response::IntoResponse, +}; +use axum_extra::extract::cookie::CookieJar; +use jsonwebtoken::{DecodingKey, Validation, decode}; + +#[derive(Debug, serde::Serialize)] +pub struct ErrorResponse { + pub status: &'static str, + pub message: String, +} + +pub async fn auth( + cookie_jar: CookieJar, + req: Request, + next: Next, +) -> Result)> { + let token = cookie_jar + .get("token") + .map(|cookie| cookie.value().to_string()) + .or_else(|| { + req.headers() + .get(axum::http::header::AUTHORIZATION) + .and_then(|auth_header| auth_header.to_str().ok()) + .and_then(|auth_value| auth_value.strip_prefix("Bearer ").map(String::from)) + }); + + let token = token.ok_or_else(|| { + let json_error = ErrorResponse { + status: "fail", + message: "You are not logged in, please provide token".to_string(), + }; + (StatusCode::UNAUTHORIZED, Json(json_error)) + })?; + + let secret_key = textsender_models::envy::environment::get_secret_main_key() + .await + .value; + + let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256); + validation.set_audience(&["icarus"]); // Must match exactly what's in the token + let _claims = decode::( + &token, + &DecodingKey::from_secret(secret_key.as_ref()), + &validation, + ) + .map_err(|err| { + eprintln!("Error: {err:?}"); + let json_error = ErrorResponse { + status: "fail", + message: "Invalid token - Error decoding claims".to_string(), + }; + (StatusCode::UNAUTHORIZED, Json(json_error)) + })? + .claims; + + Ok(next.run(req).await) +} -- 2.47.3 From a46b4321cb60693536ffd7b1697a3a54d0fd2f3c Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:21:48 -0400 Subject: [PATCH 11/42] Saving info --- src/caller/mod.rs | 130 +++++++++++++++++++++++++++++++++++++++++++++- src/config/mod.rs | 9 ++-- 2 files changed, 133 insertions(+), 6 deletions(-) diff --git a/src/caller/mod.rs b/src/caller/mod.rs index 5dd76e5..7594512 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -13,4 +13,132 @@ pub async fn root() -> &'static str { "Hello, World!" } -pub mod contact {} +pub mod contact { + pub mod request { + use serde::{Deserialize, Serialize}; + use utoipa::ToSchema; + + #[derive(Debug, Deserialize, Serialize, ToSchema)] + pub struct AddContactRequest { + pub phone_number: String, + pub first_name: String, + pub last_name: String, + pub nickname: Option, + pub user_id: uuid::Uuid, + } + } + + pub mod response { + use serde::{Deserialize, Serialize}; + use utoipa::ToSchema; + + #[derive(Debug, Default, Deserialize, Serialize, ToSchema)] + pub struct AddContactResponse { + pub message: String, + // TODO: Should be this. Update code in textsender_models + // pub data: Vec, + pub data: Vec, + } + } + + pub mod endpoint { + use axum::{Json, response::IntoResponse}; + + use crate::repo; + use crate::repo::queue as repo_queue; + + // TODO: Change the name of this endpoint. Including the function name and path + /// Endpoint to create song + #[utoipa::path( + post, + path = super::super::queue::endpoints::QUEUEMETADATA, + request_body( + content = super::request::create_metadata::Request, + description = "Data needed to create the song and save it to the filesystem", + content_type = "application/json" + ), + responses( + (status = 200, description = "Song created", body = super::response::create_metadata::Response), + (status = 400, description = "Error", body = super::response::create_metadata::Response), + (status = 505, description = "Error creating song", body = super::response::create_metadata::Response) + ) + )] + 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() { + let mut song = payload.to_song(); + song.filename = icarus_models::song::generate_filename( + icarus_models::types::MusicType::FlacExtension, + true, + ) + .unwrap(); + song.directory = icarus_envy::environment::get_root_directory().await.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"); + } + 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) => { + response.message = err.to_string(); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + 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)) + } + } + } +} + +/* +"phone_number": "+16303831708", +"first_name": "Bob", +"last_name": "De-buildor", +"nickname": "Bob", +"user_id": "{{user_id}}" +*/ diff --git a/src/config/mod.rs b/src/config/mod.rs index a768a75..8d58c8a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -14,11 +14,10 @@ pub mod init { use tower_http::timeout::TimeoutLayer; use utoipa::OpenApi; - use crate::callers::coverart as coverart_caller; - use crate::callers::queue::coverart as coverart_queue_callers; - use crate::callers::queue::metadata as metadata_queue_caller; - use crate::callers::queue::song as song_queue_callers; - use crate::callers::song as song_caller; + use crate::caller::contact as contact_caller; + use contact_caller::endpoint as contact_endpoints; + use contact_caller::response as contact_responses; + use coverart_caller::endpoint as coverart_endpoints; use coverart_caller::response as coverart_responses; use coverart_queue_callers::endpoint as coverart_queue_endpoints; -- 2.47.3 From 7e06f771924c51805205e2c99b86da9438a1b83a Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:27:47 -0400 Subject: [PATCH 12/42] Saving changes --- src/caller/mod.rs | 83 ++++++++++------------------------------------- 1 file changed, 17 insertions(+), 66 deletions(-) diff --git a/src/caller/mod.rs b/src/caller/mod.rs index 7594512..9791854 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -26,6 +26,11 @@ pub mod contact { pub nickname: Option, pub user_id: uuid::Uuid, } + + impl AddContactRequest { + pub fn is_valid(&self) -> bool { + } + } } pub mod response { @@ -45,88 +50,34 @@ pub mod contact { use axum::{Json, response::IntoResponse}; use crate::repo; - use crate::repo::queue as repo_queue; + use crate::repo::contact as contact_repo; - // TODO: Change the name of this endpoint. Including the function name and path - /// Endpoint to create song + /// Endpoint to create Contact #[utoipa::path( post, - path = super::super::queue::endpoints::QUEUEMETADATA, + path = super::super::endpoints::ADD_CONTACT, request_body( - content = super::request::create_metadata::Request, - description = "Data needed to create the song and save it to the filesystem", + content = super::request::AddContactRequest, + description = "Data needed to create a Contact", content_type = "application/json" ), responses( - (status = 200, description = "Song created", body = super::response::create_metadata::Response), - (status = 400, description = "Error", body = super::response::create_metadata::Response), - (status = 505, description = "Error creating song", body = super::response::create_metadata::Response) + (status = 201, description = "Contact created", body = super::response::AddContactResponse), + (status = 400, description = "Error", body = super::response::AddContactResponse), + (status = 500, description = "Error creating Contact", body = super::response::AddContactResponse) ) )] pub async fn create_metadata( axum::Extension(pool): axum::Extension, - axum::Json(payload): axum::Json, + axum::Json(payload): axum::Json, ) -> ( axum::http::StatusCode, - axum::Json, + axum::Json, ) { - let mut response = super::response::create_metadata::Response::default(); + let mut response = super::response::AddContactResponse::default(); if payload.is_valid() { - let mut song = payload.to_song(); - song.filename = icarus_models::song::generate_filename( - icarus_models::types::MusicType::FlacExtension, - true, - ) - .unwrap(); - song.directory = icarus_envy::environment::get_root_directory().await.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"); - } - 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) => { - response.message = err.to_string(); - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - axum::Json(response), - ) - } - } - } - Err(err) => { - response.message = err.to_string(); - (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) - } - } + (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 ed659c192079cc630faa1204047cb8def04417ba Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:33:07 -0400 Subject: [PATCH 13/42] Cleanup --- src/caller/mod.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/caller/mod.rs b/src/caller/mod.rs index 9791854..408cfc0 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -1,6 +1,7 @@ pub mod endpoints { pub const ROOT: &str = "/"; + /// Constant for adding Contact endpoint pub const ADD_CONTACT: &str = "/api/v1/contact/new"; } @@ -29,6 +30,15 @@ pub mod contact { impl AddContactRequest { pub fn is_valid(&self) -> bool { + if self.phone_number.is_empty() + || self.first_name.is_empty() + || self.last_name.is_empty() + || self.user_id.is_nil() + { + false + } else { + true + } } } } @@ -67,7 +77,7 @@ pub mod contact { (status = 500, description = "Error creating Contact", body = super::response::AddContactResponse) ) )] - pub async fn create_metadata( + pub async fn create_contact( axum::Extension(pool): axum::Extension, axum::Json(payload): axum::Json, ) -> ( @@ -77,7 +87,10 @@ pub mod contact { let mut response = super::response::AddContactResponse::default(); if payload.is_valid() { - (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) + ( + axum::http::StatusCode::NOT_IMPLEMENTED, + axum::Json(response), + ) } else { response.message = String::from("Request body is not valid"); (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) @@ -85,11 +98,3 @@ pub mod contact { } } } - -/* -"phone_number": "+16303831708", -"first_name": "Bob", -"last_name": "De-buildor", -"nickname": "Bob", -"user_id": "{{user_id}}" -*/ -- 2.47.3 From d050eea643dc1e29d68deb3f347ec7d555d79eb7 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:34:43 -0400 Subject: [PATCH 14/42] Saving changes --- src/config/mod.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 8d58c8a..decbfbe 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -18,17 +18,6 @@ pub mod init { use contact_caller::endpoint as contact_endpoints; use contact_caller::response as contact_responses; - use coverart_caller::endpoint as coverart_endpoints; - use coverart_caller::response as coverart_responses; - use coverart_queue_callers::endpoint as coverart_queue_endpoints; - use coverart_queue_callers::response as coverart_queue_responses; - use metadata_queue_caller::endpoint as metadata_queue_endpoints; - use metadata_queue_caller::response as metadata_queue_responses; - use song_caller::endpoint as song_endpoints; - use song_caller::response as song_responses; - use song_queue_callers::endpoint as song_queue_endpoints; - use song_queue_callers::response as song_queue_responses; - mod cors { pub async fn configure_cors() -> tower_http::cors::CorsLayer { let cors = tower_http::cors::CorsLayer::new() @@ -80,8 +69,8 @@ pub mod init { #[derive(utoipa::OpenApi)] #[openapi( - paths(song_queue_endpoints::queue_song,), - components(schemas(song_queue_callers::response::song_queue::Response)), + paths(contact_endpoints::create_contact,), + components(schemas(contact_caller::response::AddContactResponse)), tags( (name = "textsender API", description = "Web API to manage texting") ) -- 2.47.3 From a630f6dcdc6755383468104361bbe4033549bb32 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:38:00 -0400 Subject: [PATCH 15/42] Added it --- src/config/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index decbfbe..a24887e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -80,10 +80,10 @@ pub mod init { pub async fn routes() -> axum::Router { axum::Router::new() .route( - crate::callers::queue::endpoints::QUEUESONG, - post(crate::callers::queue::song::endpoint::queue_song).route_layer( - axum::middleware::from_fn(crate::auth::auth::), - ), + crate::caller::endpoints::ADD_CONTACT, + post(contact_endpoints::create_contact).route_layer(axum::middleware::from_fn( + crate::auth::auth::, + )), ) .layer(cors::configure_cors().await) } -- 2.47.3 From 567853134c2ecfeadd1bccdbc3ff8c257cd88d66 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:40:37 -0400 Subject: [PATCH 16/42] Updating sample env files --- .env.docker.sample | 1 + .env.local.sample | 1 + 2 files changed, 2 insertions(+) diff --git a/.env.docker.sample b/.env.docker.sample index 4bcc074..41af1c3 100644 --- a/.env.docker.sample +++ b/.env.docker.sample @@ -5,6 +5,7 @@ DB_PASSWORD=password DB_HOST=main_db DB_PORT=5432 DB_SSLMODE=disable +DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1" diff --git a/.env.local.sample b/.env.local.sample index e47c1f5..a781640 100644 --- a/.env.local.sample +++ b/.env.local.sample @@ -5,6 +5,7 @@ DB_PASSWORD=password DB_HOST=localhost DB_PORT=5432 DB_SSLMODE=disable +DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1" -- 2.47.3 From 39697a82e14a8b3d5be5d9bdf99c80673194a10f Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 15:51:56 -0400 Subject: [PATCH 17/42] Adding code --- src/caller/mod.rs | 47 ++++++++++++++++++++++++++++++----------------- src/config/mod.rs | 10 +++++----- src/repo/mod.rs | 2 +- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/caller/mod.rs b/src/caller/mod.rs index 408cfc0..6374623 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -22,23 +22,18 @@ pub mod contact { #[derive(Debug, Deserialize, Serialize, ToSchema)] pub struct AddContactRequest { pub phone_number: String, - pub first_name: String, - pub last_name: String, + pub firstname: Option, + pub lastname: Option, pub nickname: Option, pub user_id: uuid::Uuid, } impl AddContactRequest { pub fn is_valid(&self) -> bool { - if self.phone_number.is_empty() - || self.first_name.is_empty() - || self.last_name.is_empty() + self.phone_number.is_empty() + || self.firstname.is_none() + || self.lastname.is_none() || self.user_id.is_nil() - { - false - } else { - true - } } } } @@ -57,10 +52,10 @@ pub mod contact { } pub mod endpoint { - use axum::{Json, response::IntoResponse}; + // use axum::{Json, response::IntoResponse}; - use crate::repo; - use crate::repo::contact as contact_repo; + // use crate::repo; + // use crate::repo::contact as contact_repo; /// Endpoint to create Contact #[utoipa::path( @@ -87,10 +82,28 @@ pub mod contact { let mut response = super::response::AddContactResponse::default(); if payload.is_valid() { - ( - axum::http::StatusCode::NOT_IMPLEMENTED, - axum::Json(response), - ) + let contact = textsender_models::contact::Contact { + firstname: payload.firstname, + lastname: payload.lastname, + phone_number: payload.phone_number, + user_id: Some(payload.user_id), + ..Default::default() + }; + match crate::repo::contact::insert(&pool, &contact).await { + Ok(_) => ( + axum::http::StatusCode::NOT_IMPLEMENTED, + axum::Json(response), + ), + Err(err) => { + eprintln!("Error: {err:?}"); + response.message = String::from("Contact not created"); + + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + 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/config/mod.rs b/src/config/mod.rs index a24887e..b79f3a9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -10,13 +10,13 @@ pub mod host { pub mod init { use std::time::Duration; - use axum::routing::{delete, get, patch, post}; + use axum::routing::post; use tower_http::timeout::TimeoutLayer; use utoipa::OpenApi; use crate::caller::contact as contact_caller; use contact_caller::endpoint as contact_endpoints; - use contact_caller::response as contact_responses; + // use contact_caller::response as contact_responses; mod cors { pub async fn configure_cors() -> tower_http::cors::CorsLayer { @@ -57,8 +57,8 @@ pub mod init { _ => { // Development (default): Allow localhost origins cors.allow_origin(vec![ - "http://localhost:8000".parse().unwrap(), - "http://127.0.0.1:8000".parse().unwrap(), + "http://localhost:9081".parse().unwrap(), + "http://127.0.0.1:9081".parse().unwrap(), "http://localhost:4200".parse().unwrap(), "http://127.0.0.1:4200".parse().unwrap(), ]) @@ -74,7 +74,7 @@ pub mod init { tags( (name = "textsender API", description = "Web API to manage texting") ) -)] + )] struct ApiDoc; pub async fn routes() -> axum::Router { diff --git a/src/repo/mod.rs b/src/repo/mod.rs index fab9efc..0700239 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -13,7 +13,7 @@ pub mod contact { .bind(&contact.lastname) .bind(&contact.nickname) .bind(&contact.phone_number) - .bind(&contact.user_id) + .bind(contact.user_id) .execute(pool) .await; -- 2.47.3 From fdb5e23e901c334bdd56cf0df22613e979f12476 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 16:07:29 -0400 Subject: [PATCH 18/42] Updating --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64b2264..5f3e251 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2325,8 +2325,8 @@ dependencies = [ [[package]] name = "textsender_models" -version = "0.3.0" -source = "git+ssh://git@git.kundeng.us/phoenix/textsender_models.git?tag=v0.3.0#d504108745b7b97a02eac24e16763cdb5e731f2b" +version = "0.3.1" +source = "git+ssh://git@git.kundeng.us/phoenix/textsender_models.git?tag=v0.3.1-25-ca3b0c92d4-111#ca3b0c92d401ff8ee9b06b1506af042f622667da" dependencies = [ "const_format", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index eb85782..fd082c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } -textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.3.0" } +textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.3.1-25-ca3b0c92d4-111" } [dev-dependencies] common-multipart-rfc7578 = { version = "0.7.0" } -- 2.47.3 From 773049835c99428561bbe61df07b88a8ac114b88 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 16:10:00 -0400 Subject: [PATCH 19/42] Almost --- src/caller/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/caller/mod.rs b/src/caller/mod.rs index 6374623..d8bac0f 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -47,7 +47,7 @@ pub mod contact { pub message: String, // TODO: Should be this. Update code in textsender_models // pub data: Vec, - pub data: Vec, + pub data: Vec, } } @@ -90,10 +90,11 @@ pub mod contact { ..Default::default() }; match crate::repo::contact::insert(&pool, &contact).await { - Ok(_) => ( - axum::http::StatusCode::NOT_IMPLEMENTED, - axum::Json(response), - ), + Ok(_) => { + response.message = String::from(super::super::response::SUCCESSFUL); + + (axum::http::StatusCode::CREATED, axum::Json(response)) + } Err(err) => { eprintln!("Error: {err:?}"); response.message = String::from("Contact not created"); -- 2.47.3 From d399e4ba29183d366aaf349305326e44f98fda43 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 16:12:24 -0400 Subject: [PATCH 20/42] Cleanup --- src/config/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index b79f3a9..1bde5a1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -57,8 +57,12 @@ pub mod init { _ => { // Development (default): Allow localhost origins cors.allow_origin(vec![ - "http://localhost:9081".parse().unwrap(), - "http://127.0.0.1:9081".parse().unwrap(), + format!("http://localhost:{}", super::super::host::PORT) + .parse() + .unwrap(), + format!("http://127.0.0.1:{}", super::super::host::PORT) + .parse() + .unwrap(), "http://localhost:4200".parse().unwrap(), "http://127.0.0.1:4200".parse().unwrap(), ]) -- 2.47.3 From 3474076599adbf9f6b25d012b44404e09a399db2 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 16:19:23 -0400 Subject: [PATCH 21/42] Commenting out catapult for now --- docker-compose.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 56a3f79..969c385 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: - textsender_api-network restart: unless-stopped # Optional: Restart policy - # --- Web API auth --- + # --- Auth API --- auth_api: build: # Might want to change the naming convention at some point for the repo names. textsender-models, textsender_auth, textesender-api, etc @@ -44,23 +44,23 @@ services: # volumes: # - ./path/to/your/web-api-repo:/app - # --- songparser service --- - catapult: - build: - context: ../catapult - ssh: ["default"] - dockerfile: Dockerfile - container_name: catapult - restart: unless-stopped - env_file: - - ../catapult/.env - depends_on: - - api - - main_db - - auth_api - - auth_db - networks: - - textsender_api-network + # --- catapult service --- +# catapult: +# build: +# context: ../catapult +# ssh: ["default"] +# dockerfile: Dockerfile +# container_name: catapult +# restart: unless-stopped +# env_file: +# - ../catapult/.env +# depends_on: +# - api +# - main_db +# - auth_api +# - auth_db +# networks: +# - textsender_api-network # PostgreSQL Database Service -- 2.47.3 From 50104a4db09a125014de1bc7a66421ddcef61e57 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 17:05:34 -0400 Subject: [PATCH 22/42] Updating sample env files --- .env.docker.sample | 14 +++++++------- .env.local.sample | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.env.docker.sample b/.env.docker.sample index 41af1c3..7600a38 100644 --- a/.env.docker.sample +++ b/.env.docker.sample @@ -1,11 +1,11 @@ JWT_SECRET=NULqYIzgt28bTiyziCd7IOO7b6LnWDW! -DB_NAME=textsender_db -DB_USER=textsender -DB_PASSWORD=password -DB_HOST=main_db -DB_PORT=5432 -DB_SSLMODE=disable -DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} +DB_MAIN_NAME=textsender_db +DB_MAIN_USER=textsender +DB_MAIN_PASSWORD=password +DB_MAIN_HOST=main_db +DB_MAIN_PORT=5432 +DB_MAIN_SSLMODE=disable +DATABASE_URL=postgres://${DB_MAIN_USER}:${DB_MAIN_PASSWORD}@${DB_MAIN_HOST}:${DB_MAIN_PORT}/${DB_MAIN_NAME} TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1" diff --git a/.env.local.sample b/.env.local.sample index a781640..166eb33 100644 --- a/.env.local.sample +++ b/.env.local.sample @@ -1,11 +1,11 @@ JWT_SECRET=NULqYIzgt28bTiyziCd7IOO7b6LnWDW! -DB_NAME=textsender_db -DB_USER=textsender -DB_PASSWORD=password -DB_HOST=localhost -DB_PORT=5432 -DB_SSLMODE=disable -DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} +DB_MAIN_NAME=textsender_db +DB_MAIN_USER=textsender +DB_MAIN_PASSWORD=password +DB_MAIN_HOST=localhost +DB_MAIN_PORT=5432 +DB_MAIN_SSLMODE=disable +DATABASE_URL=postgres://${DB_MAIN_USER}:${DB_MAIN_PASSWORD}@${DB_MAIN_HOST}:${DB_MAIN_PORT}/${DB_MAIN_NAME} TWILIO_AUTH_SID=9M438C93R943U4329MCU43C34U TWILIO_SERVICE_SID=9M4J3X8439U398NUVT3342MC349C348T TWILIO_AUTH_TOKEN="f4a1f2b0b79ea3735078c2d8ee9684e1" -- 2.47.3 From c57d5f5d9088b9b1654c61e034aaacdc25aae580 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 17:05:45 -0400 Subject: [PATCH 23/42] Updating docker compose --- docker-compose.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 969c385..e6756a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,9 +70,9 @@ services: container_name: textsender_api_db # Optional: Give the container a specific name environment: # These MUST match the user, password, and database name in the DATABASE_URL above - POSTGRES_USER: ${POSTGRES_MAIN_USER:-textsender_api} - POSTGRES_PASSWORD: ${POSTGRES_MAIN_PASSWORD:-password} - POSTGRES_DB: ${POSTGRES_MAIN_DB:-textsender_api_db} + POSTGRES_USER: ${DB_MAIN_USER:-textsender_api} + POSTGRES_PASSWORD: ${DB_MAIN_PASSWORD:-password} + POSTGRES_DB: ${DB_MAIN_NAME:-textsender_api_db} volumes: # Persist database data using a named volume - postgres_data:/var/lib/postgresql @@ -96,9 +96,9 @@ services: container_name: textsender_api_auth_db # Optional: Give the container a specific name environment: # These MUST match the user, password, and database name in the DATABASE_URL above - POSTGRES_USER: ${POSTGRES_AUTH_USER:-textsender_api_op} - POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password} - POSTGRES_DB: ${POSTGRES_AUTH_DB:-textsender_api_auth_db} + POSTGRES_USER: ${DB_AUTH_USER:-textsender_api_op} + POSTGRES_PASSWORD: ${DB_AUTH_PASSWORD:-password} + POSTGRES_DB: ${DB_AUTH_DB:-textsender_api_auth_db} volumes: # Persist database data using a named volume - postgres_data_auth:/var/lib/postgresql -- 2.47.3 From 80a79f2b96ea87f83fc237324fb764d1021cf129 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 17:11:34 -0400 Subject: [PATCH 24/42] Changed refernece --- src/auth/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 49be85a..84b764e 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -41,7 +41,7 @@ pub async fn auth( .value; let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256); - validation.set_audience(&["icarus"]); // Must match exactly what's in the token + validation.set_audience(&["textsender"]); // Must match exactly what's in the token let _claims = decode::( &token, &DecodingKey::from_secret(secret_key.as_ref()), -- 2.47.3 From 876293ece2785d0c55d2610236b4a48046b174ea Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 17:17:33 -0400 Subject: [PATCH 25/42] Migration changes --- migrations/20260613211440_init.sql | 12 ++++ migrations/schema.sql | 98 +++++++++++++++--------------- 2 files changed, 61 insertions(+), 49 deletions(-) create mode 100644 migrations/20260613211440_init.sql diff --git a/migrations/20260613211440_init.sql b/migrations/20260613211440_init.sql new file mode 100644 index 0000000..63ebb21 --- /dev/null +++ b/migrations/20260613211440_init.sql @@ -0,0 +1,12 @@ +-- Add migration script here +CREATE EXTENSION IF NOT EXISTS pgcrypto; +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +CREATE TABLE IF NOT EXISTS "contacts" ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + phone_number TEXT NOT NULL, + user_id UUID NOT NULL, + first_name TEXT NULL, + last_name TEXT NULL, + nickname TEXT NULL +); diff --git a/migrations/schema.sql b/migrations/schema.sql index 23587c6..9ca3c39 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -1,49 +1,49 @@ -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - -DROP TABLE IF EXISTS contacts CASCADE; -DROP TABLE IF EXISTS messages CASCADE; -DROP TABLE IF EXISTS scheduled_messages CASCADE; -DROP TABLE IF EXISTS scheduled_message_events CASCADE; -DROP TABLE IF EXISTS message_event_responses CASCADE; - -CREATE TABLE IF NOT EXISTS contacts ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - phone_number TEXT NOT NULL, - user_id UUID NOT NULL, - first_name TEXT NULL, - last_name TEXT NULL, - nickname TEXT NULL -); - -CREATE TABLE IF NOT EXISTS messages ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - content TEXT NOT NULL, - user_id UUID NOT NULL -); - -CREATE TABLE IF NOT EXISTS scheduled_messages ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - scheduled timestamptz NOT NULL, - created timestamptz DEFAULT now(), - status TEXT CHECK (status IN ('PENDING', 'READY', 'PROCESSING', 'DONE')), - user_id UUID NOT NULL -); - -CREATE TABLE IF NOT EXISTS scheduled_message_events ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - contact_id UUID NOT NULL, - message_id UUID NOT NULL, - scheduled_message_id UUID NOT NULL, - created timestamptz DEFAULT now() -); - -CREATE TABLE IF NOT EXISTS message_event_responses ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - scheduled_message_event_id UUID NULL, - response JSONB NOT NULL, - user_id UUID NOT NULL, - sent timestamptz NOT NULL, - contact_id UUID NULL, - message_id UUID NULL, - status TEXT CHECK (status IN ('INSTANT', 'SCHEDULED')) -); +-- CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +-- +-- DROP TABLE IF EXISTS contacts CASCADE; +-- DROP TABLE IF EXISTS messages CASCADE; +-- DROP TABLE IF EXISTS scheduled_messages CASCADE; +-- DROP TABLE IF EXISTS scheduled_message_events CASCADE; +-- DROP TABLE IF EXISTS message_event_responses CASCADE; +-- +-- CREATE TABLE IF NOT EXISTS contacts ( +-- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), +-- phone_number TEXT NOT NULL, +-- user_id UUID NOT NULL, +-- first_name TEXT NULL, +-- last_name TEXT NULL, +-- nickname TEXT NULL +-- ); +-- +-- CREATE TABLE IF NOT EXISTS messages ( +-- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), +-- content TEXT NOT NULL, +-- user_id UUID NOT NULL +-- ); +-- +-- CREATE TABLE IF NOT EXISTS scheduled_messages ( +-- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), +-- scheduled timestamptz NOT NULL, +-- created timestamptz DEFAULT now(), +-- status TEXT CHECK (status IN ('PENDING', 'READY', 'PROCESSING', 'DONE')), +-- user_id UUID NOT NULL +-- ); +-- +-- CREATE TABLE IF NOT EXISTS scheduled_message_events ( +-- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), +-- contact_id UUID NOT NULL, +-- message_id UUID NOT NULL, +-- scheduled_message_id UUID NOT NULL, +-- created timestamptz DEFAULT now() +-- ); +-- +-- CREATE TABLE IF NOT EXISTS message_event_responses ( +-- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), +-- scheduled_message_event_id UUID NULL, +-- response JSONB NOT NULL, +-- user_id UUID NOT NULL, +-- sent timestamptz NOT NULL, +-- contact_id UUID NULL, +-- message_id UUID NULL, +-- status TEXT CHECK (status IN ('INSTANT', 'SCHEDULED')) +-- ); -- 2.47.3 From 5f77958d9cc7c0baaca7ff9907fd631f6c3932a7 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 17:22:52 -0400 Subject: [PATCH 26/42] Docker changes --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e6756a5..a46e834 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -98,13 +98,13 @@ services: # These MUST match the user, password, and database name in the DATABASE_URL above POSTGRES_USER: ${DB_AUTH_USER:-textsender_api_op} POSTGRES_PASSWORD: ${DB_AUTH_PASSWORD:-password} - POSTGRES_DB: ${DB_AUTH_DB:-textsender_api_auth_db} + POSTGRES_DB: ${DB_AUTH_NAME:-textsender_api_auth_db} volumes: # Persist database data using a named volume - postgres_data_auth:/var/lib/postgresql ports: # Optional: Expose port 5432 ONLY if you need to connect directly from your host machine (e.g., for debugging) - - "5433:5432" + - "5432:5432" healthcheck: # Checks if Postgres is ready to accept connections test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] -- 2.47.3 From e3ce0beb305c9d3da147dbf0d5d4385be04bd641 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 17:24:16 -0400 Subject: [PATCH 27/42] Port change --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a46e834..89f8ae3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,7 +78,7 @@ services: - postgres_data:/var/lib/postgresql ports: # Optional: Expose port 5432 ONLY if you need to connect directly from your host machine (e.g., for debugging) - - "5432:5432" + - "5433:5432" healthcheck: # Checks if Postgres is ready to accept connections test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] -- 2.47.3 From 0a513d859cf6b5f8dbd1b2561577fb81cdc185f8 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 17:39:58 -0400 Subject: [PATCH 28/42] Some changes --- docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 89f8ae3..94a00f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,7 +78,7 @@ services: - postgres_data:/var/lib/postgresql ports: # Optional: Expose port 5432 ONLY if you need to connect directly from your host machine (e.g., for debugging) - - "5433:5432" + - "5432:5432" healthcheck: # Checks if Postgres is ready to accept connections test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] @@ -96,15 +96,15 @@ services: container_name: textsender_api_auth_db # Optional: Give the container a specific name environment: # These MUST match the user, password, and database name in the DATABASE_URL above - POSTGRES_USER: ${DB_AUTH_USER:-textsender_api_op} + POSTGRES_USER: ${DB_AUTH_USER:-textsender_op} POSTGRES_PASSWORD: ${DB_AUTH_PASSWORD:-password} - POSTGRES_DB: ${DB_AUTH_NAME:-textsender_api_auth_db} + POSTGRES_DB: ${DB_AUTH_NAME:-textsender_auth_db} volumes: # Persist database data using a named volume - postgres_data_auth:/var/lib/postgresql ports: # Optional: Expose port 5432 ONLY if you need to connect directly from your host machine (e.g., for debugging) - - "5432:5432" + - "5433:5432" healthcheck: # Checks if Postgres is ready to accept connections test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] -- 2.47.3 From 0e27595492cdcf6c6b0e764da1a7fbf39640321b Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 17:48:36 -0400 Subject: [PATCH 29/42] This fixed it --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 94a00f0..7fe4794 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -96,7 +96,7 @@ services: container_name: textsender_api_auth_db # Optional: Give the container a specific name environment: # These MUST match the user, password, and database name in the DATABASE_URL above - POSTGRES_USER: ${DB_AUTH_USER:-textsender_op} + POSTGRES_USER: ${DB_AUTH_USER:-textsender_auth} POSTGRES_PASSWORD: ${DB_AUTH_PASSWORD:-password} POSTGRES_DB: ${DB_AUTH_NAME:-textsender_auth_db} volumes: -- 2.47.3 From d72de41a393e05c172f6c0287ce6b345d26eda41 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 18:05:47 -0400 Subject: [PATCH 30/42] bump: textsender_models --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fd082c9..318c74b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } -textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.3.1-25-ca3b0c92d4-111" } +textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.3.2" } [dev-dependencies] common-multipart-rfc7578 = { version = "0.7.0" } -- 2.47.3 From 0b542b3f268b5726ac23a33014d4e278ed359d1e Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 18:50:44 -0400 Subject: [PATCH 31/42] Updated migrations --- migrations/20260613211440_init.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/20260613211440_init.sql b/migrations/20260613211440_init.sql index 63ebb21..ef5cc2b 100644 --- a/migrations/20260613211440_init.sql +++ b/migrations/20260613211440_init.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS "contacts" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), phone_number TEXT NOT NULL, user_id UUID NOT NULL, - first_name TEXT NULL, - last_name TEXT NULL, + firstname TEXT NULL, + lastname TEXT NULL, nickname TEXT NULL ); -- 2.47.3 From 306511074a110f59f4b7ef23fff4871883b440d3 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 18:50:58 -0400 Subject: [PATCH 32/42] Yes --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/caller/mod.rs | 13 ++++++------- src/repo/mod.rs | 22 ++++++++++------------ 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f3e251..6286798 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2325,8 +2325,8 @@ dependencies = [ [[package]] name = "textsender_models" -version = "0.3.1" -source = "git+ssh://git@git.kundeng.us/phoenix/textsender_models.git?tag=v0.3.1-25-ca3b0c92d4-111#ca3b0c92d401ff8ee9b06b1506af042f622667da" +version = "0.3.3" +source = "git+ssh://git@git.kundeng.us/phoenix/textsender_models.git?tag=v0.3.3-27-ec46697714-111#ec466977146cdfca34b4fa50d82d63025a9aa82a" dependencies = [ "const_format", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index 318c74b..edb7b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } -textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.3.2" } +textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.3.3-27-ec46697714-111" } [dev-dependencies] common-multipart-rfc7578 = { version = "0.7.0" } diff --git a/src/caller/mod.rs b/src/caller/mod.rs index d8bac0f..7eadcad 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -45,8 +45,6 @@ pub mod contact { #[derive(Debug, Default, Deserialize, Serialize, ToSchema)] pub struct AddContactResponse { pub message: String, - // TODO: Should be this. Update code in textsender_models - // pub data: Vec, pub data: Vec, } } @@ -54,8 +52,7 @@ pub mod contact { pub mod endpoint { // use axum::{Json, response::IntoResponse}; - // use crate::repo; - // use crate::repo::contact as contact_repo; + use crate::repo::contact as contact_repo; /// Endpoint to create Contact #[utoipa::path( @@ -82,16 +79,18 @@ pub mod contact { let mut response = super::response::AddContactResponse::default(); if payload.is_valid() { - let contact = textsender_models::contact::Contact { + let mut contact = textsender_models::contact::Contact { firstname: payload.firstname, lastname: payload.lastname, phone_number: payload.phone_number, user_id: Some(payload.user_id), ..Default::default() }; - match crate::repo::contact::insert(&pool, &contact).await { - Ok(_) => { + match contact_repo::insert(&pool, &contact).await { + Ok(id) => { + contact.id = Some(id); response.message = String::from(super::super::response::SUCCESSFUL); + response.data.push(contact); (axum::http::StatusCode::CREATED, axum::Json(response)) } diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 0700239..7434ddb 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,12 +1,14 @@ pub mod contact { + use sqlx::Row; + pub async fn insert( pool: &sqlx::PgPool, contact: &textsender_models::contact::Contact, - ) -> Result<(), sqlx::Error> { - let result = sqlx::query( + ) -> Result { + match sqlx::query( r#" INSERT INTO "contacts" (firstname, lastname, nickname, phone_number, user_id) - VALUES($1, $2, $3, $4, $5); + VALUES($1, $2, $3, $4, $5) RETURNING id; "#, ) .bind(&contact.firstname) @@ -14,16 +16,12 @@ pub mod contact { .bind(&contact.nickname) .bind(&contact.phone_number) .bind(contact.user_id) - .execute(pool) - .await; - - match result { + .fetch_one(pool) + .await + { Ok(row) => { - if row.rows_affected() > 0 { - Ok(()) - } else { - Err(sqlx::Error::RowNotFound) - } + let id: uuid::Uuid = row.try_get("id")?; + Ok(id) } Err(_) => Err(sqlx::Error::RowNotFound), } -- 2.47.3 From 264b2662124d0471bd839644f6921cfed8fde715 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:00:00 -0400 Subject: [PATCH 33/42] Got it --- src/caller/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/caller/mod.rs b/src/caller/mod.rs index 7eadcad..9839b1a 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -30,10 +30,7 @@ pub mod contact { impl AddContactRequest { pub fn is_valid(&self) -> bool { - self.phone_number.is_empty() - || self.firstname.is_none() - || self.lastname.is_none() - || self.user_id.is_nil() + !self.phone_number.is_empty() || !self.user_id.is_nil() } } } -- 2.47.3 From 1bed731e3d322f8fc9b0e23c3f16e96c1c90dd5d Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:08:15 -0400 Subject: [PATCH 34/42] bump: textsender_models --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6286798..6df4b2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2326,7 +2326,7 @@ dependencies = [ [[package]] name = "textsender_models" version = "0.3.3" -source = "git+ssh://git@git.kundeng.us/phoenix/textsender_models.git?tag=v0.3.3-27-ec46697714-111#ec466977146cdfca34b4fa50d82d63025a9aa82a" +source = "git+ssh://git@git.kundeng.us/phoenix/textsender_models.git?tag=v0.3.3#c09fa36b15601b37f65b8baac0d479c30d2c1e44" dependencies = [ "const_format", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index edb7b50..a6ee3f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } -textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.3.3-27-ec46697714-111" } +textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.3.3" } [dev-dependencies] common-multipart-rfc7578 = { version = "0.7.0" } -- 2.47.3 From 6f5029d143235a32d571842932f6e3945dc24d3a Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:20:42 -0400 Subject: [PATCH 35/42] Adding test --- tests/test.rs | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 tests/test.rs diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..d947449 --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,196 @@ +use std::io::Write; + +use common_multipart_rfc7578::client::multipart::{Body as MultipartBody, Form as MultipartForm}; +use tower::ServiceExt; + +use textsender_api::db; + +mod db_mgr { + use std::str::FromStr; + + pub const LIMIT: usize = 6; + + pub async fn get_pool() -> Result { + let tm_db_url = textsender_models::envy::environment::get_db_url() + .await + .value; + 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().await.unwrap() + &"_" + &uuid::Uuid::new_v4().to_string()[..LIMIT]; + db_name + } + + pub async fn connect_to_db(db_name: &str) -> Result { + let db_url = textsender_models::envy::environment::get_db_url() + .await + .value; + 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 async fn get_database_name() -> Result> { + let database_url = textsender_models::envy::environment::get_db_url() + .await + .value; + 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()) + } + } + + pub async fn migrations(pool: &sqlx::PgPool) { + // Run migrations using the sqlx::migrate! macro + // Assumes your test migrations are in a ./test_migrations folder relative to Cargo.toml + sqlx::migrate!("./migrations") + .run(pool) + .await + .expect("Failed to run migrations"); + } +} + +mod init { + pub async fn app(pool: sqlx::PgPool) -> axum::Router { + textsender_api::config::init::routes() + .await + .layer(axum::Extension(pool)) + .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) + .layer(tower_http::timeout::TimeoutLayer::new( + std::time::Duration::from_secs(300), + )) + } +} + +mod util { + pub async fn resp_to_bytes( + response: axum::response::Response, + ) -> Result { + axum::body::to_bytes(response.into_body(), std::usize::MAX).await + } + + pub async fn get_resp_data(response: axum::response::Response) -> Data + where + Data: for<'a> serde::Deserialize<'a>, + { + let body = resp_to_bytes(response).await.unwrap(); + serde_json::from_slice(&body).unwrap() + } + + pub async fn format_url_with_value(endpoint: &str, value: &uuid::Uuid) -> String { + let last = endpoint.len() - 5; + format!("{}/{value}", &endpoint[0..last]) + } +} + +pub fn token_fields() -> (String, String, String) { + ( + String::from("What a twist!"), + String::from("icarus_test"), + String::from("icarus"), + ) +} + +pub const TEST_USER_ID: uuid::Uuid = uuid::uuid!("cc938368-615a-4694-b2ca-6e122fa31c52"); + +pub async fn test_token() -> Result { + let key: String = textsender_models::envy::environment::get_secret_main_key() + .await + .value; + let (message, issuer, audience) = token_fields(); + + let token_resource = textsender_models::token::TokenResource { + message: message, + issuer: issuer, + audiences: vec![audience], + user_id: TEST_USER_ID, + }; + + match textsender_models::token::create_token(&key, &token_resource, time::Duration::hours(1)) { + Ok((access_token, _some_time)) => Ok(access_token), + Err(err) => Err(err), + } +} + +pub async fn bearer_auth() -> String { + let token = match test_token().await { + Ok(access_token) => access_token, + Err(err) => { + assert!(false, "Error: {err:?}"); + String::new() + } + }; + + format!("Bearer {token}") +} + +mod request {} + +#[tokio::test] +async fn test_create_contact() { + 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 request::create_contact(&app).await { + Ok(response) => { + let resp = util::get_resp_data::< + textsender_api::caller::contact::response::AddContactResponse, + >(response) + .await; + assert_eq!(false, resp.data.is_empty(), "Should not be empty"); + } + Err(err) => { + assert!(false, "Error: {:?}", err); + } + }; + + let _ = db_mgr::drop_database(&tm_pool, &db_name).await; +} -- 2.47.3 From f199d02d81d97c5463a51dfe632d0ff78f6e468f Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:26:41 -0400 Subject: [PATCH 36/42] Test changes --- tests/test.rs | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/test.rs b/tests/test.rs index d947449..47042a3 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -157,7 +157,43 @@ pub async fn bearer_auth() -> String { format!("Bearer {token}") } -mod request {} +mod request { + // use common_multipart_rfc7578::client::multipart::{ + // Body as MultipartBody, Form as MultipartForm, + // }; + use tower::ServiceExt; + + pub async fn create_contact( + app: &axum::Router, + ) -> Result { + let payload = serde_json::json!({ + "phone_number": super::TEST_CONTACT_PHONE_NUMBER, + "user_id": super::TEST_USER_ID, + }); + // Create multipart form + // let mut form = MultipartForm::default(); + // let _ = form.add_file("flac", "tests/I/track01.flac"); + + // Create request + // let content_type = form.content_type(); + // let body = MultipartBody::from(form); + let req = axum::http::Request::builder() + .method(axum::http::Method::POST) + .uri(textsender_api::caller::endpoints::ADD_CONTACT) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .header( + axum::http::header::AUTHORIZATION, + super::bearer_auth().await, + ) + .body(axum::body::Body::from(payload.to_string())) + // .body(axum::body::Body::from_stream(body)) + .unwrap(); + app.clone().oneshot(req).await + } +} + +/// Test contact phone number +pub const TEST_CONTACT_PHONE_NUMBER: &str = "+10123456789"; #[tokio::test] async fn test_create_contact() { -- 2.47.3 From bacf8b66459699d23a6c17737a6b82e37a464f6b Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:31:32 -0400 Subject: [PATCH 37/42] Test fixes --- tests/test.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test.rs b/tests/test.rs index 47042a3..052c0da 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,7 +1,7 @@ -use std::io::Write; +// use std::io::Write; -use common_multipart_rfc7578::client::multipart::{Body as MultipartBody, Form as MultipartForm}; -use tower::ServiceExt; +// use common_multipart_rfc7578::client::multipart::{Body as MultipartBody, Form as MultipartForm}; +// use tower::ServiceExt; use textsender_api::db; @@ -73,6 +73,7 @@ mod db_mgr { } } + /* pub async fn migrations(pool: &sqlx::PgPool) { // Run migrations using the sqlx::migrate! macro // Assumes your test migrations are in a ./test_migrations folder relative to Cargo.toml @@ -81,6 +82,7 @@ mod db_mgr { .await .expect("Failed to run migrations"); } + */ } mod init { @@ -89,7 +91,8 @@ mod init { .await .layer(axum::Extension(pool)) .layer(axum::extract::DefaultBodyLimit::max(1024 * 1024 * 1024)) - .layer(tower_http::timeout::TimeoutLayer::new( + .layer(tower_http::timeout::TimeoutLayer::with_status_code( + axum::http::StatusCode::OK, std::time::Duration::from_secs(300), )) } @@ -110,10 +113,12 @@ mod util { serde_json::from_slice(&body).unwrap() } + /* pub async fn format_url_with_value(endpoint: &str, value: &uuid::Uuid) -> String { let last = endpoint.len() - 5; format!("{}/{value}", &endpoint[0..last]) } + */ } pub fn token_fields() -> (String, String, String) { -- 2.47.3 From e4ea7bfd697db8017fa7cca3b8dac6b615b4e160 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:33:37 -0400 Subject: [PATCH 38/42] Workflow fix --- .gitea/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/workflow.yml b/.gitea/workflows/workflow.yml index ba4c8ca..19f3bbd 100644 --- a/.gitea/workflows/workflow.yml +++ b/.gitea/workflows/workflow.yml @@ -75,7 +75,7 @@ jobs: # Define DATABASE_URL for tests to use DATABASE_URL: postgresql://${{ secrets.DB_TEST_USER || 'testuser' }}:${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}@postgres:${{ secrets.DB_PORT || 5432 }}/${{ secrets.DB_TEST_NAME || 'testdb' }} RUST_LOG: info # Optional: configure test log level - SECRET_KEY: ${{ secrets.SECRET_KEY }} + SECRE_MAIN_KEY: ${{ secrets.SECRET_KEY }} # Make SSH agent available if tests fetch private dependencies SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }} ENABLE_REGISTRATION: 'TRUE' -- 2.47.3 From 75d7907070e6087df59ac3bc2b8b3074ec98a11c Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:33:45 -0400 Subject: [PATCH 39/42] Test change --- tests/test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test.rs b/tests/test.rs index 052c0da..2882b88 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -124,8 +124,8 @@ mod util { pub fn token_fields() -> (String, String, String) { ( String::from("What a twist!"), - String::from("icarus_test"), - String::from("icarus"), + String::from("textsender_test"), + String::from("textsender"), ) } -- 2.47.3 From 2c02be488fe17683a3535be5cfd3b7bc538a1c1b Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:35:34 -0400 Subject: [PATCH 40/42] Typo --- .gitea/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/workflow.yml b/.gitea/workflows/workflow.yml index 19f3bbd..ea96dad 100644 --- a/.gitea/workflows/workflow.yml +++ b/.gitea/workflows/workflow.yml @@ -75,7 +75,7 @@ jobs: # Define DATABASE_URL for tests to use DATABASE_URL: postgresql://${{ secrets.DB_TEST_USER || 'testuser' }}:${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}@postgres:${{ secrets.DB_PORT || 5432 }}/${{ secrets.DB_TEST_NAME || 'testdb' }} RUST_LOG: info # Optional: configure test log level - SECRE_MAIN_KEY: ${{ secrets.SECRET_KEY }} + SECRET_MAIN_KEY: ${{ secrets.SECRET_KEY }} # Make SSH agent available if tests fetch private dependencies SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }} ENABLE_REGISTRATION: 'TRUE' -- 2.47.3 From 899497e8e2996577db89eab67d25596faf40b6ce Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:41:46 -0400 Subject: [PATCH 41/42] Test fix --- tests/test.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/test.rs b/tests/test.rs index 2882b88..257fe59 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -175,13 +175,7 @@ mod request { "phone_number": super::TEST_CONTACT_PHONE_NUMBER, "user_id": super::TEST_USER_ID, }); - // Create multipart form - // let mut form = MultipartForm::default(); - // let _ = form.add_file("flac", "tests/I/track01.flac"); - // Create request - // let content_type = form.content_type(); - // let body = MultipartBody::from(form); let req = axum::http::Request::builder() .method(axum::http::Method::POST) .uri(textsender_api::caller::endpoints::ADD_CONTACT) @@ -191,7 +185,6 @@ mod request { super::bearer_auth().await, ) .body(axum::body::Body::from(payload.to_string())) - // .body(axum::body::Body::from_stream(body)) .unwrap(); app.clone().oneshot(req).await } -- 2.47.3 From a3eeb1d753fe1847d01e71d944680696be20561b Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 13 Jun 2026 19:41:58 -0400 Subject: [PATCH 42/42] bump: textsender_api --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6df4b2c..2ec2379 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2295,7 +2295,7 @@ dependencies = [ [[package]] name = "textsender_api" -version = "0.1.3" +version = "0.1.4" dependencies = [ "axum", "axum-extra", diff --git a/Cargo.toml b/Cargo.toml index a6ee3f6..9462e9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "textsender_api" -version = "0.1.3" +version = "0.1.4" edition = "2024" rust-version = "1.95" -- 2.47.3