From 7873e8ed00b7da2f8c607b00205800086e715082 Mon Sep 17 00:00:00 2001 From: phoenix Date: Thu, 18 Jun 2026 18:39:24 -0400 Subject: [PATCH] Create scheduled message event (#17) Reviewed-on: http://git.kundeng.us/phoenix/textsender_api/pulls/17 --- Cargo.lock | 53 +----- Cargo.toml | 3 +- migrations/20260613211440_init.sql | 19 +++ src/caller/message/scheduling.rs | 106 +++++++++++- src/caller/mod.rs | 2 + src/config/mod.rs | 6 + src/repo/message/event.rs | 111 +++++++++++++ src/repo/{message.rs => message/mod.rs} | 3 + src/repo/message/scheduling.rs | 211 ++++++++++++++++++++++++ src/repo/mod.rs | 1 - src/repo/scheduling.rs | 103 ------------ tests/test.rs | 134 +++++++++++++-- 12 files changed, 586 insertions(+), 166 deletions(-) create mode 100644 src/repo/message/event.rs rename src/repo/{message.rs => message/mod.rs} (98%) create mode 100644 src/repo/message/scheduling.rs delete mode 100644 src/repo/scheduling.rs diff --git a/Cargo.lock b/Cargo.lock index b600799..f342aea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,22 +204,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "common-multipart-rfc7578" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08d53b5e0c302c5830cfa7511ba0edc3f241c691a95c0d184dfb761e11a6cc2" -dependencies = [ - "bytes", - "futures-core", - "futures-util", - "http", - "mime", - "mime_guess", - "rand", - "thiserror 1.0.69", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1075,7 +1059,7 @@ dependencies = [ "regex", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", "time", ] @@ -1982,7 +1966,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.18", + "thiserror", "time", ] @@ -2071,7 +2055,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.18", + "thiserror", "time", "tokio", "tokio-stream", @@ -2155,7 +2139,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror", "time", "tracing", "uuid", @@ -2194,7 +2178,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror", "time", "tracing", "uuid", @@ -2220,7 +2204,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.18", + "thiserror", "time", "tracing", "url", @@ -2293,11 +2277,10 @@ dependencies = [ [[package]] name = "textsender_api" -version = "0.1.11" +version = "0.1.12" dependencies = [ "axum", "axum-extra", - "common-multipart-rfc7578", "futures", "josekit", "jsonwebtoken", @@ -2333,33 +2316,13 @@ dependencies = [ "uuid", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c11c93b..253522b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "textsender_api" -version = "0.1.11" +version = "0.1.12" edition = "2024" rust-version = "1.96" @@ -25,6 +25,5 @@ utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.4.1-29-ad632fca16-111" } [dev-dependencies] -common-multipart-rfc7578 = { version = "0.7.0" } url = { version = "2.5.8" } tempfile = { version = "3.27.0" } diff --git a/migrations/20260613211440_init.sql b/migrations/20260613211440_init.sql index 48d8e47..7be9c7b 100644 --- a/migrations/20260613211440_init.sql +++ b/migrations/20260613211440_init.sql @@ -24,3 +24,22 @@ CREATE TABLE IF NOT EXISTS "scheduled_messages" ( 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 gen_random_uuid(), + 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 gen_random_uuid(), + 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')) +); diff --git a/src/caller/message/scheduling.rs b/src/caller/message/scheduling.rs index 29f310a..5604f67 100644 --- a/src/caller/message/scheduling.rs +++ b/src/caller/message/scheduling.rs @@ -21,6 +21,21 @@ pub mod request { pub id: Option, pub user_id: Option, } + + #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] + pub struct CreateScheduleMessageEventRequest { + pub contact_id: uuid::Uuid, + pub message_id: uuid::Uuid, + pub scheduled_message_id: uuid::Uuid, + } + + impl CreateScheduleMessageEventRequest { + pub fn is_valid(&self) -> bool { + !self.contact_id.is_nil() + || !self.message_id.is_nil() + || !self.scheduled_message_id.is_nil() + } + } } pub mod response { @@ -38,10 +53,18 @@ pub mod response { pub message: String, pub data: Vec, } + + #[derive(Debug, Default, Deserialize, Serialize, ToSchema)] + pub struct CreateScheduleMessageEventResponse { + pub message: String, + pub data: Vec, + } } pub mod endpoint { - use crate::repo::scheduling as scheduling_repo; + use crate::repo::contact as contact_repo; + use crate::repo::message as message_repo; + use message_repo::scheduling as scheduling_repo; /// Endpoint to create a Scheudled Message #[utoipa::path( @@ -174,4 +197,85 @@ pub mod endpoint { scheduled_time .is_some_and(|t| t - time::OffsetDateTime::now_utc() >= time::Duration::minutes(10)) } + + /// Endpoint to create a Scheudled Message Event + #[utoipa::path( + post, + path = crate::caller::endpoints::CREATE_SCHEDULED_MESSAGE_EVENT, + request_body( + content = super::request::CreateScheduleMessageEventRequest, + description = "Data needed to create a Scheduled Message Event", + content_type = "application/json" + ), + responses( + (status = 201, description = "Scheduled Message Event created", body = super::response::CreateScheduleMessageEventResponse), + (status = 400, description = "Error", body = super::response::CreateScheduleMessageEventResponse), + (status = 500, description = "Error creating Scheduled Message Event", body = super::response::CreateScheduleMessageEventResponse) + ) +)] + pub async fn create_scheduled_message_event( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, + ) -> ( + axum::http::StatusCode, + axum::Json, + ) { + let mut response = super::response::CreateScheduleMessageEventResponse::default(); + + if payload.is_valid() { + match scheduling_repo::get(&pool, &payload.scheduled_message_id).await { + Ok(_) => {} + Err(err) => { + eprintln!("Error: {err:?}"); + response.message = String::from("Scheduled Message does not exist"); + return (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)); + } + } + + match contact_repo::get(&pool, &payload.contact_id).await { + Ok(_) => {} + Err(err) => { + eprintln!("Error: {err:?}"); + response.message = String::from("Contact does not exist"); + return (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)); + } + } + + match message_repo::get(&pool, &payload.message_id).await { + Ok(_) => {} + Err(err) => { + eprintln!("Error: {err:?}"); + response.message = String::from("Contact does not exist"); + return (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)); + } + } + + let mut scme = textsender_models::message::scheduling::ScheduledMessageEvent { + contact_id: payload.contact_id, + message_id: payload.message_id, + scheduled_message_id: payload.scheduled_message_id, + ..Default::default() + }; + match scheduling_repo::sched_msg_event::insert(&pool, &scme).await { + Ok((id, created)) => { + scme.id = id; + scme.created = Some(created); + response.message = String::from(super::super::super::response::SUCCESSFUL); + response.data.push(scme); + (axum::http::StatusCode::CREATED, axum::Json(response)) + } + Err(err) => { + eprintln!("Error: {err:?}"); + response.message = String::from("Error inserting"); + ( + 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/caller/mod.rs b/src/caller/mod.rs index 11dff9c..5d68d7f 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -18,6 +18,8 @@ pub mod endpoints { pub const SCHEDULE_MESSAGE: &str = "/api/v1/schedule/message"; /// Constant for getting scheduled message endpoint pub const GET_SCHEDULE_MESSAGE: &str = "/api/v1/schedule/message"; + /// Constant for creating Scheduled Message Event endpoint + pub const CREATE_SCHEDULED_MESSAGE_EVENT: &str = "/api/v1/schedule/message/event"; } pub mod response { diff --git a/src/config/mod.rs b/src/config/mod.rs index ce2e588..6cb9a0d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -136,6 +136,12 @@ pub mod init { axum::middleware::from_fn(crate::auth::auth::), ), ) + .route( + crate::caller::endpoints::CREATE_SCHEDULED_MESSAGE_EVENT, + post(scheduling_endpoints::create_scheduled_message_event).route_layer( + axum::middleware::from_fn(crate::auth::auth::), + ), + ) .layer(cors::configure_cors().await) } diff --git a/src/repo/message/event.rs b/src/repo/message/event.rs new file mode 100644 index 0000000..c5898c6 --- /dev/null +++ b/src/repo/message/event.rs @@ -0,0 +1,111 @@ +use sqlx::Row; + +pub async fn insert( + pool: &sqlx::PgPool, + event: &textsender_models::message::event::MessageEventResponse, +) -> Result { + match sqlx::query( + r#" + INSERT INTO "message_event_responses" (scheduled_message_event_id, response, user_id, sent, contact_id, message_id, status) + VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING id; + "#, + ) + .bind(event.scheduled_message_event_id) + .bind(&event.response) + .bind(event.user_id) + .bind(event.sent) + .bind(event.contact_id) + .bind(event.message_id) + .bind(&event.status) + .fetch_one(pool) + .await + { + Ok(row) => { + let id: uuid::Uuid = row.try_get("id")?; + Ok(id) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get( + pool: &sqlx::PgPool, + id: &uuid::Uuid, +) -> Result { + match sqlx::query( + r#" + SELECT id, scheduled_message_event_id, response, user_id, sent, contact_id, message_id, status FROM "message_event_responses" + WHERE + id = $1; + "#, + ) + .bind(id) + .fetch_one(pool) + .await + { + Ok(row) => match parse_row(&row).await { + Ok(event) => Ok(event), + Err(err) => Err(err), + }, + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_with_user_id( + pool: &sqlx::PgPool, + user_id: &uuid::Uuid, +) -> Result, sqlx::Error> { + match sqlx::query( + r#" + SELECT id, scheduled_message_event_id, response, user_id, sent, contact_id, message_id, status FROM "message_event_responses" + WHERE + user_id = $1; + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await + { + Ok(rows) => { + let mut events: Vec = + Vec::new(); + for row in rows { + match parse_row(&row).await { + Ok(event) => { + events.push(event); + } + Err(err) => { + return Err(err); + } + } + } + + Ok(events) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +async fn parse_row( + row: &sqlx::postgres::PgRow, +) -> Result { + let id: uuid::Uuid = row.try_get("id")?; + let response: String = row.try_get("response")?; + let status: String = row.try_get("status")?; + let contact_id: uuid::Uuid = row.try_get("contact_id")?; + let message_id: uuid::Uuid = row.try_get("message_id")?; + let sent: time::OffsetDateTime = row.try_get("sent")?; + let scheduled_message_id: uuid::Uuid = row.try_get("scheduled_message_id")?; + let user_id: uuid::Uuid = row.try_get("user_id")?; + + Ok(textsender_models::message::event::MessageEventResponse { + id, + response, + contact_id, + message_id, + status, + sent: Some(sent), + scheduled_message_event_id: scheduled_message_id, + user_id, + }) +} diff --git a/src/repo/message.rs b/src/repo/message/mod.rs similarity index 98% rename from src/repo/message.rs rename to src/repo/message/mod.rs index 0e3b015..09b0414 100644 --- a/src/repo/message.rs +++ b/src/repo/message/mod.rs @@ -1,3 +1,6 @@ +pub mod event; +pub mod scheduling; + use sqlx::Row; pub async fn insert( diff --git a/src/repo/message/scheduling.rs b/src/repo/message/scheduling.rs new file mode 100644 index 0000000..246a9c9 --- /dev/null +++ b/src/repo/message/scheduling.rs @@ -0,0 +1,211 @@ +use sqlx::Row; + +pub async fn insert( + pool: &sqlx::PgPool, + scheduled_message: &textsender_models::message::scheduling::ScheduledMessage, + user_id: &uuid::Uuid, +) -> Result<(uuid::Uuid, time::OffsetDateTime), sqlx::Error> { + match sqlx::query( + r#" + INSERT INTO "scheduled_messages" (scheduled, status, user_id) + VALUES($1, $2, $3) RETURNING id, created; + "#, + ) + .bind(scheduled_message.scheduled) + .bind(&scheduled_message.status) + .bind(user_id) + .fetch_one(pool) + .await + { + Ok(row) => { + let id: uuid::Uuid = row.try_get("id")?; + let created: time::OffsetDateTime = row.try_get("created")?; + Ok((id, created)) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get( + pool: &sqlx::PgPool, + id: &uuid::Uuid, +) -> Result { + match sqlx::query( + r#" + SELECT id, scheduled, created, status, user_id FROM "scheduled_messages" + WHERE + id = $1; + "#, + ) + .bind(id) + .fetch_one(pool) + .await + { + Ok(row) => match parse_row(&row).await { + Ok(scheduled_message) => Ok(scheduled_message), + Err(err) => Err(err), + }, + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_with_user_id( + pool: &sqlx::PgPool, + user_id: &uuid::Uuid, +) -> Result, sqlx::Error> { + match sqlx::query( + r#" + SELECT id, scheduled, created, status, user_id FROM "scheduled_messages" + WHERE + user_id = $1; + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await + { + Ok(rows) => { + let mut messages: Vec = + Vec::new(); + for row in rows { + match parse_row(&row).await { + Ok(scheduled_message) => { + messages.push(scheduled_message); + } + Err(err) => { + return Err(err); + } + } + } + + Ok(messages) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +async fn parse_row( + row: &sqlx::postgres::PgRow, +) -> Result { + let id: uuid::Uuid = row.try_get("id")?; + let scheduled: time::OffsetDateTime = row.try_get("scheduled")?; + let status: String = row.try_get("status")?; + let created: time::OffsetDateTime = row.try_get("created")?; + let user_id: uuid::Uuid = row.try_get("user_id")?; + + Ok(textsender_models::message::scheduling::ScheduledMessage { + id, + scheduled: Some(scheduled), + created: Some(created), + status, + user_id, + }) +} + +pub mod sched_msg_event { + use sqlx::Row; + + pub async fn insert( + pool: &sqlx::PgPool, + scheduled_message_event: &textsender_models::message::scheduling::ScheduledMessageEvent, + ) -> Result<(uuid::Uuid, time::OffsetDateTime), sqlx::Error> { + match sqlx::query( + r#" + INSERT INTO "scheduled_message_events" (contact_id, message_id, scheduled_message_id) + VALUES($1, $2, $3) RETURNING id, created; + "#, + ) + .bind(scheduled_message_event.contact_id) + .bind(scheduled_message_event.message_id) + .bind(scheduled_message_event.scheduled_message_id) + .fetch_one(pool) + .await + { + Ok(row) => { + let id: uuid::Uuid = row.try_get("id")?; + let created: time::OffsetDateTime = row.try_get("created")?; + Ok((id, created)) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } + } + + pub async fn get( + pool: &sqlx::PgPool, + id: &uuid::Uuid, + ) -> Result { + match sqlx::query( + r#" + SELECT id, contact_id, message_id, scheduled_message_id, created FROM "scheduled_message_events" + WHERE + id = $1; + "#, + ) + .bind(id) + .fetch_one(pool) + .await + { + Ok(row) => match parse_row(&row).await { + Ok(scheduled_message_event) => Ok(scheduled_message_event), + Err(err) => Err(err), + }, + Err(_) => Err(sqlx::Error::RowNotFound), + } + } + + pub async fn get_with_scheduled_message_id( + pool: &sqlx::PgPool, + scheduled_message_id: &uuid::Uuid, + ) -> Result, sqlx::Error> + { + match sqlx::query( + r#" + SELECT id, contact_id, message_id, scheduled_message_id, created FROM "scheduled_message_events" + WHERE + scheduled_message_id = $1; + "#, + ) + .bind(scheduled_message_id) + .fetch_all(pool) + .await + { + Ok(rows) => { + let mut scheduled_message_events: Vec = + Vec::new(); + for row in rows { + match parse_row(&row).await { + Ok(scheduled_message_event) => { + scheduled_message_events.push(scheduled_message_event); + } + Err(err) => { + return Err(err); + } + } + } + + Ok(scheduled_message_events) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } + } + + async fn parse_row( + row: &sqlx::postgres::PgRow, + ) -> Result { + let id: uuid::Uuid = row.try_get("id")?; + let contact_id: uuid::Uuid = row.try_get("contact_id")?; + let message_id: uuid::Uuid = row.try_get("message_id")?; + let scheduled_message_id: uuid::Uuid = row.try_get("scheduled_message_id")?; + let created: time::OffsetDateTime = row.try_get("created")?; + + Ok( + textsender_models::message::scheduling::ScheduledMessageEvent { + id, + contact_id, + message_id, + scheduled_message_id, + created: Some(created), + }, + ) + } +} diff --git a/src/repo/mod.rs b/src/repo/mod.rs index d185221..2af9758 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,3 +1,2 @@ pub mod contact; pub mod message; -pub mod scheduling; diff --git a/src/repo/scheduling.rs b/src/repo/scheduling.rs deleted file mode 100644 index 97af52c..0000000 --- a/src/repo/scheduling.rs +++ /dev/null @@ -1,103 +0,0 @@ -use sqlx::Row; - -pub async fn insert( - pool: &sqlx::PgPool, - scheduled_message: &textsender_models::message::scheduling::ScheduledMessage, - user_id: &uuid::Uuid, -) -> Result<(uuid::Uuid, time::OffsetDateTime), sqlx::Error> { - match sqlx::query( - r#" - INSERT INTO "scheduled_messages" (scheduled, status, user_id) - VALUES($1, $2, $3) RETURNING id, created; - "#, - ) - .bind(scheduled_message.scheduled) - .bind(&scheduled_message.status) - .bind(user_id) - .fetch_one(pool) - .await - { - Ok(row) => { - let id: uuid::Uuid = row.try_get("id")?; - let created: time::OffsetDateTime = row.try_get("created")?; - Ok((id, created)) - } - Err(_) => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get( - pool: &sqlx::PgPool, - id: &uuid::Uuid, -) -> Result { - match sqlx::query( - r#" - SELECT id, scheduled, created, status, user_id FROM "scheduled_messages" - WHERE - id = $1; - "#, - ) - .bind(id) - .fetch_one(pool) - .await - { - Ok(row) => match parse_row(&row).await { - Ok(scheduled_message) => Ok(scheduled_message), - Err(err) => Err(err), - }, - Err(_) => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_with_user_id( - pool: &sqlx::PgPool, - user_id: &uuid::Uuid, -) -> Result, sqlx::Error> { - match sqlx::query( - r#" - SELECT id, scheduled, created, status, user_id FROM "scheduled_messages" - WHERE - user_id = $1; - "#, - ) - .bind(user_id) - .fetch_all(pool) - .await - { - Ok(rows) => { - let mut messages: Vec = - Vec::new(); - for row in rows { - match parse_row(&row).await { - Ok(scheduled_message) => { - messages.push(scheduled_message); - } - Err(err) => { - return Err(err); - } - } - } - - Ok(messages) - } - Err(_) => Err(sqlx::Error::RowNotFound), - } -} - -async fn parse_row( - row: &sqlx::postgres::PgRow, -) -> Result { - let id: uuid::Uuid = row.try_get("id")?; - let scheduled: time::OffsetDateTime = row.try_get("scheduled")?; - let status: String = row.try_get("status")?; - let created: time::OffsetDateTime = row.try_get("created")?; - let user_id: uuid::Uuid = row.try_get("user_id")?; - - Ok(textsender_models::message::scheduling::ScheduledMessage { - id, - scheduled: Some(scheduled), - created: Some(created), - status, - user_id, - }) -} diff --git a/tests/test.rs b/tests/test.rs index 0660fee..6d2b843 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,6 +1,3 @@ -// use std::io::Write; - -// use common_multipart_rfc7578::client::multipart::{Body as MultipartBody, Form as MultipartForm}; use textsender_api::db; mod db_mgr { @@ -178,6 +175,19 @@ pub async fn test_token() -> Result { } } +pub fn test_valid_schedulable_time() -> time::OffsetDateTime { + let now = time::OffsetDateTime::now_utc(); + now + std::time::Duration::from_secs(60 * 60) +} + +pub fn convert_time_to_iso(time: time::OffsetDateTime) -> Result { + use time::format_description::well_known::Iso8601; + match time.format(&Iso8601::DEFAULT) { + Ok(converted) => Ok(converted), + Err(err) => Err(err), + } +} + pub async fn bearer_auth() -> String { let token = match test_token().await { Ok(access_token) => access_token, @@ -191,9 +201,6 @@ pub async fn bearer_auth() -> String { } mod request { - // use common_multipart_rfc7578::client::multipart::{ - // Body as MultipartBody, Form as MultipartForm, - // }; use tower::ServiceExt; pub async fn create_contact( @@ -317,15 +324,13 @@ mod request { } pub mod scheduling { - use time::format_description::well_known::Iso8601; use tower::ServiceExt; pub async fn create_scheduled_message( app: &axum::Router, ) -> Result { - let now = time::OffsetDateTime::now_utc(); - let scheduled = now + std::time::Duration::from_secs(60 * 60); - let scheduled = match scheduled.format(&Iso8601::DEFAULT) { + let scheduled = super::super::super::test_valid_schedulable_time(); + let scheduled = match super::super::super::convert_time_to_iso(scheduled) { Ok(t) => t, Err(err) => { assert!(false, "Error: {err:?}"); @@ -373,6 +378,36 @@ mod request { Err(err) => Err(err), } } + + pub async fn create_scheduled_message_event( + app: &axum::Router, + contact_id: &uuid::Uuid, + message_id: &uuid::Uuid, + scheduled_message_id: &uuid::Uuid, + ) -> Result { + let payload = serde_json::json!({ + "contact_id": contact_id, + "message_id": message_id, + "scheduled_message_id": scheduled_message_id + }); + + println!("Payload: {payload:?}"); + + match super::super::run_post( + Some(&payload), + textsender_api::caller::endpoints::CREATE_SCHEDULED_MESSAGE_EVENT, + axum::http::Method::POST, + true, + ) + .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), + } + } } } @@ -628,8 +663,6 @@ async fn test_schedule_message() { match request::message::scheduling::create_scheduled_message(&app).await { Ok(response) => { - eprintln!("Response: {response:?}"); - println!("Response: {response:?}"); let resp = util::get_resp_data::< textsender_api::caller::message::scheduling::response::ScheduleMessageResponse, >(response) @@ -653,8 +686,6 @@ async fn test_get_scheduled_message() { match request::message::scheduling::create_scheduled_message(&app).await { Ok(response) => { - eprintln!("Response: {response:?}"); - println!("Response: {response:?}"); let resp = util::get_resp_data::< textsender_api::caller::message::scheduling::response::ScheduleMessageResponse, >(response) @@ -687,3 +718,78 @@ async fn test_get_scheduled_message() { let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } + +#[tokio::test] +async fn test_create_scheduled_message_event() { + 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:?}"); + } + } + + let _ = db_mgr::drop_database(&tm_pool, &db_name).await; +}