diff --git a/Cargo.lock b/Cargo.lock index dc3eaa9..29d1fdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2293,7 +2293,7 @@ dependencies = [ [[package]] name = "textsender_api" -version = "0.1.7" +version = "0.1.8" dependencies = [ "axum", "axum-extra", diff --git a/Cargo.toml b/Cargo.toml index ccf8ec5..5797080 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "textsender_api" -version = "0.1.7" +version = "0.1.8" edition = "2024" rust-version = "1.96" diff --git a/migrations/20260613211440_init.sql b/migrations/20260613211440_init.sql index ef5cc2b..caf9db4 100644 --- a/migrations/20260613211440_init.sql +++ b/migrations/20260613211440_init.sql @@ -10,3 +10,9 @@ CREATE TABLE IF NOT EXISTS "contacts" ( lastname TEXT NULL, nickname TEXT NULL ); + +CREATE TABLE IF NOT EXISTS "messages" ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + content TEXT NOT NULL, + user_id UUID NOT NULL +); diff --git a/migrations/schema.sql b/migrations/schema.sql deleted file mode 100644 index 9ca3c39..0000000 --- a/migrations/schema.sql +++ /dev/null @@ -1,49 +0,0 @@ --- 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')) --- ); diff --git a/src/caller/message.rs b/src/caller/message.rs new file mode 100644 index 0000000..05d8280 --- /dev/null +++ b/src/caller/message.rs @@ -0,0 +1,84 @@ +pub mod request { + use serde::{Deserialize, Serialize}; + use utoipa::ToSchema; + + #[derive(Debug, Deserialize, Serialize, ToSchema)] + pub struct AddMessageRequest { + pub content: String, + pub user_id: uuid::Uuid, + } + + impl AddMessageRequest { + pub fn is_valid(&self) -> bool { + !self.content.is_empty() || !self.user_id.is_nil() + } + } +} + +pub mod response { + use serde::{Deserialize, Serialize}; + use utoipa::ToSchema; + + #[derive(Debug, Default, Deserialize, Serialize, ToSchema)] + pub struct AddMessageResponse { + pub message: String, + pub data: Vec, + } +} + +pub mod endpoint { + use crate::repo::message as message_repo; + + /// Endpoint to create Message + #[utoipa::path( + post, + path = super::super::endpoints::ADD_MESSAGE, + request_body( + content = super::request::AddMessageRequest, + description = "Data needed to create a Message", + content_type = "application/json" + ), + responses( + (status = 201, description = "Message created", body = super::response::AddMessageResponse), + (status = 400, description = "Error", body = super::response::AddMessageResponse), + (status = 500, description = "Error creating Message", body = super::response::AddMessageResponse) + ) +)] + pub async fn create_message( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, + ) -> ( + axum::http::StatusCode, + axum::Json, + ) { + let mut response = super::response::AddMessageResponse::default(); + + if payload.is_valid() { + let mut msg = textsender_models::message::Message { + content: payload.content, + user_id: Some(payload.user_id), + ..Default::default() + }; + match message_repo::insert(&pool, &msg, &payload.user_id).await { + Ok(message_id) => { + msg.id = Some(message_id); + response.data.push(msg); + response.message = String::from("Message created"); + (axum::http::StatusCode::CREATED, 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/caller/mod.rs b/src/caller/mod.rs index 17bc20a..85b102e 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -1,4 +1,5 @@ pub mod contact; +pub mod message; pub mod endpoints { pub const ROOT: &str = "/"; @@ -9,6 +10,8 @@ pub mod endpoints { pub const GET_CONTACT: &str = "/api/v1/contact"; /// Constant for updating names of a Contact endpoint pub const UPDATE_CONTACT_NAME: &str = "/api/v1/contact/update"; + /// Constant for adding message endpoint + pub const ADD_MESSAGE: &str = "/api/v1/message/new"; } pub mod response { diff --git a/src/config/mod.rs b/src/config/mod.rs index 105fb1c..f8a03e9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -15,8 +15,11 @@ pub mod init { use utoipa::OpenApi; use crate::caller::contact as contact_caller; + use crate::caller::message as message_caller; use contact_caller::endpoint as contact_endpoints; - // use contact_caller::response as contact_responses; + use contact_caller::response as contact_responses; + use message_caller::endpoint as message_endpoints; + use message_caller::response as message_responses; mod cors { pub async fn configure_cors() -> tower_http::cors::CorsLayer { @@ -73,8 +76,9 @@ pub mod init { #[derive(utoipa::OpenApi)] #[openapi( - paths(contact_endpoints::create_contact,), - components(schemas(contact_caller::response::AddContactResponse)), + paths(contact_endpoints::create_contact, + message_endpoints::create_message), + components(schemas(contact_responses::AddContactResponse, message_responses::AddMessageResponse)), tags( (name = "textsender API", description = "Web API to manage texting") ) @@ -101,6 +105,12 @@ pub mod init { axum::middleware::from_fn(crate::auth::auth::), ), ) + .route( + crate::caller::endpoints::ADD_MESSAGE, + post(message_endpoints::create_message).route_layer(axum::middleware::from_fn( + crate::auth::auth::, + )), + ) .layer(cors::configure_cors().await) } diff --git a/src/repo/message.rs b/src/repo/message.rs new file mode 100644 index 0000000..312e893 --- /dev/null +++ b/src/repo/message.rs @@ -0,0 +1,25 @@ +use sqlx::Row; + +pub async fn insert( + pool: &sqlx::PgPool, + message: &textsender_models::message::Message, + user_id: &uuid::Uuid, +) -> Result { + match sqlx::query( + r#" + INSERT INTO "messages" (content, user_id) + VALUES($1, $2) RETURNING id; + "#, + ) + .bind(&message.content) + .bind(user_id) + .fetch_one(pool) + .await + { + Ok(row) => { + let id: uuid::Uuid = row.try_get("id")?; + Ok(id) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } +} diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 40d6353..2af9758 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1 +1,2 @@ pub mod contact; +pub mod message;