From e6f14917f02f68ead961aad3be5646d231857394 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 20 Jun 2026 12:03:46 -0400 Subject: [PATCH] Fetch schedule message endpoint (#21) Reviewed-on: http://git.kundeng.us/phoenix/textsender_api/pulls/21 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/caller/message/scheduling.rs | 51 +++++++++++-- src/caller/mod.rs | 2 + src/config/mod.rs | 6 ++ src/repo/message/scheduling.rs | 30 ++++++++ tests/test.rs | 126 +++++++++++++++++++++++++++++++ 7 files changed, 211 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eae15e8..2eb269f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2277,7 +2277,7 @@ dependencies = [ [[package]] name = "textsender_api" -version = "0.1.14" +version = "0.1.15" dependencies = [ "axum", "axum-extra", diff --git a/Cargo.toml b/Cargo.toml index 80587bf..1de4543 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "textsender_api" -version = "0.1.14" +version = "0.1.15" edition = "2024" rust-version = "1.96" diff --git a/src/caller/message/scheduling.rs b/src/caller/message/scheduling.rs index af1eef7..d146889 100644 --- a/src/caller/message/scheduling.rs +++ b/src/caller/message/scheduling.rs @@ -52,15 +52,10 @@ pub mod request { impl UpdateScheduledMessageStatusRequest { pub fn is_valid(&self) -> bool { if !self.status.is_empty() || !self.scheduled_message_id.is_nil() { - if self.status == textsender_models::message::scheduling::PENDING + self.status == textsender_models::message::scheduling::PENDING || self.status == textsender_models::message::scheduling::READY || self.status == textsender_models::message::scheduling::PROCESSING || self.status == textsender_models::message::scheduling::DONE - { - true - } else { - false - } } else { false } @@ -104,6 +99,8 @@ pub mod response { pub struct ChangedStatus { pub old_status: String, } + + pub use ScheduleMessageResponse as FetchScheduledMessageResponse; } pub mod endpoint { @@ -243,6 +240,48 @@ pub mod endpoint { .is_some_and(|t| t - time::OffsetDateTime::now_utc() >= time::Duration::minutes(10)) } + /// Endpoint to fetch Scheduled Message that is ready + #[utoipa::path( + get, + path = crate::caller::endpoints::FETCH_SCHEDULED_MESSAGE, + responses( + (status = 200, description = "Scheduled Message found", body = super::response::FetchScheduledMessageResponse), + (status = 400, description = "Error getting Scheduled Message", body = super::response::FetchScheduledMessageResponse) + ) + )] + pub async fn fetch_scheduled_message( + axum::Extension(pool): axum::Extension, + ) -> ( + axum::http::StatusCode, + axum::Json, + ) { + let mut response = super::response::FetchScheduledMessageResponse::default(); + + match scheduling_repo::fetch(&pool).await { + Ok(scheduled_message) => { + response.data.push(scheduled_message); + response.message = String::from(super::super::super::response::SUCCESSFUL); + + (axum::http::StatusCode::OK, axum::Json(response)) + } + Err(err) => match err { + sqlx::Error::RowNotFound => { + response.message = String::from("Nothing to fetch"); + + (axum::http::StatusCode::NO_CONTENT, axum::Json(response)) + } + _ => { + eprintln!("Error: {err:?}"); + response.message = String::from("Error"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response), + ) + } + }, + } + } + /// Endpoint to update status of Scheduled Message #[utoipa::path( patch, diff --git a/src/caller/mod.rs b/src/caller/mod.rs index b8ef7dd..db43e41 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -26,6 +26,8 @@ pub mod endpoints { pub const GET_SCHEDULED_MESSAGE_EVENT: &str = "/api/v1/schedule/message/event"; /// Constant for deleting Scheduled Message Event endpoint pub const DELETE_SCHEDULED_MESSAGE_EVENT: &str = "/api/v1/schedule/message/event/{id}"; + /// Constant for fetching Scheduled Message endpoint + pub const FETCH_SCHEDULED_MESSAGE: &str = "/api/v1/schedule/message/fetch"; } pub mod response { diff --git a/src/config/mod.rs b/src/config/mod.rs index d09b4e0..17d8485 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -160,6 +160,12 @@ pub mod init { axum::middleware::from_fn(crate::auth::auth::), ), ) + .route( + crate::caller::endpoints::FETCH_SCHEDULED_MESSAGE, + get(scheduling_endpoints::fetch_scheduled_message).route_layer( + axum::middleware::from_fn(crate::auth::auth::), + ), + ) .layer(cors::configure_cors().await) } diff --git a/src/repo/message/scheduling.rs b/src/repo/message/scheduling.rs index 36141a8..1604350 100644 --- a/src/repo/message/scheduling.rs +++ b/src/repo/message/scheduling.rs @@ -84,6 +84,36 @@ pub async fn get_with_user_id( } } +pub async fn fetch( + pool: &sqlx::PgPool, +) -> Result { + match sqlx::query( + r#" + UPDATE "scheduled_messages" + SET status = $1 + WHERE id = ( + SELECT id FROM "scheduled_messages" + WHERE status = $2 + ORDER BY id + FOR UPDATE SKIP LOCKED + LIMIT 1 + ) + RETURNING id, scheduled, created, status, user_id + "#, + ) + .bind(textsender_models::message::scheduling::PROCESSING) + .bind(textsender_models::message::scheduling::READY) + .fetch_one(pool) + .await + { + Ok(row) => match parse_row(&row).await { + Ok(scheduled_message) => Ok(scheduled_message), + Err(err) => Err(err), + }, + Err(err) => Err(err), + } +} + pub async fn update_status( pool: &sqlx::PgPool, id: &uuid::Uuid, diff --git a/tests/test.rs b/tests/test.rs index 1519c09..53e2aa0 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -377,6 +377,25 @@ mod request { } } + pub async fn fetch_scheduled_message( + app: &axum::Router, + ) -> Result { + match super::super::run_post( + None, + textsender_api::caller::endpoints::FETCH_SCHEDULED_MESSAGE, + axum::http::Method::GET, + false, + ) + .await + { + Ok(req) => match app.clone().oneshot(req).await { + Ok(response) => Ok(response), + Err(err) => Err(axum::http::Error::from(err)), + }, + Err(err) => Err(err), + } + } + pub async fn update_scheduled_message_status( app: &axum::Router, id: &uuid::Uuid, @@ -1143,3 +1162,110 @@ async fn test_update_scheduled_message_status() { let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } + +#[tokio::test] +async fn test_fetch_scheduled_message() { + let (tm_pool, db_name, pool) = db_mgr::get_database_ready().await; + let app = init::app(pool).await; + + let mut contact_id: uuid::Uuid = uuid::Uuid::nil(); + let mut message_id: uuid::Uuid = uuid::Uuid::nil(); + let mut scheduled_message_id: uuid::Uuid = uuid::Uuid::nil(); + + 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"); + contact_id = resp.data[0].id.unwrap(); + } + Err(err) => { + assert!(false, "Error: {:?}", err); + } + }; + + match request::message::create_message(&app).await { + Ok(response) => { + let resp = util::get_resp_data::< + textsender_api::caller::message::response::AddMessageResponse, + >(response) + .await; + assert_eq!(false, resp.data.is_empty(), "Should not be empty"); + let message = &resp.data[0]; + message_id = message.id.unwrap(); + } + Err(err) => { + assert!(false, "Error: {:?}", err); + } + }; + + match request::message::scheduling::create_scheduled_message(&app).await { + Ok(response) => { + let resp = util::get_resp_data::< + textsender_api::caller::message::scheduling::response::ScheduleMessageResponse, + >(response) + .await; + assert_eq!(false, resp.data.is_empty(), "Should not be empty"); + scheduled_message_id = resp.data[0].id; + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + + match request::message::scheduling::create_scheduled_message_event( + &app, + &contact_id, + &message_id, + &scheduled_message_id, + ) + .await + { + Ok(response) => { + let resp = util::get_resp_data::< + textsender_api::caller::message::scheduling::response::CreateScheduleMessageEventResponse, + >(response) + .await; + assert_eq!(false, resp.data.is_empty(), "Should not be empty"); + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + + match request::message::scheduling::update_scheduled_message_status( + &app, + &scheduled_message_id, + textsender_models::message::scheduling::READY, + ) + .await + { + Ok(response) => { + let resp = util::get_resp_data::< + textsender_api::caller::message::scheduling::response::UpdateScheduledMessageStatusResponse, + >(response) + .await; + assert_eq!(false, resp.data.is_empty(), "Should not be empty"); + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + + match request::message::scheduling::fetch_scheduled_message(&app).await { + Ok(response) => { + let resp = util::get_resp_data::< + textsender_api::caller::message::scheduling::response::FetchScheduledMessageResponse, + >(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; +}