From 37bce752ffdaeca0f431a6d36cad0f96118f4676 Mon Sep 17 00:00:00 2001 From: phoenix Date: Mon, 15 Jun 2026 22:32:10 -0400 Subject: [PATCH] Adding code for endpoint to update contact --- src/caller/contact.rs | 124 ++++++++++++++++++++++++++ src/caller/mod.rs | 2 + src/config/mod.rs | 6 ++ src/repo/contact.rs | 201 ++++++++++++++++++++++++++++++++++++++++++ src/repo/mod.rs | 181 +------------------------------------ 5 files changed, 334 insertions(+), 180 deletions(-) create mode 100644 src/repo/contact.rs diff --git a/src/caller/contact.rs b/src/caller/contact.rs index d1d296d..aa4aed4 100644 --- a/src/caller/contact.rs +++ b/src/caller/contact.rs @@ -22,6 +22,24 @@ pub mod request { pub id: Option, pub user_id: Option, } + + #[derive(Debug, Deserialize, Serialize, ToSchema)] + pub struct UpdateContactNamesRequest { + pub id: uuid::Uuid, + pub firstname: Option, + pub lastname: Option, + pub nickname: Option, + } + + impl UpdateContactNamesRequest { + pub fn is_valid(&self) -> bool { + if self.id.is_nil() { + false + } else { + self.firstname.is_some() || self.lastname.is_some() || self.nickname.is_some() + } + } + } } pub mod response { @@ -36,6 +54,22 @@ pub mod response { pub use AddContactResponse as GetContactResponse; + #[derive(Debug, Default, Deserialize, Serialize, ToSchema)] + pub struct UpdateContactNamesResponse { + pub message: String, + pub data: Vec, + } + + #[derive(Debug, Default, Deserialize, Serialize, ToSchema)] + pub struct UpdateContactNameChange { + pub old_firstname: Option, + pub old_lastname: Option, + pub old_nickname: Option, + pub new_firstname: Option, + pub new_lastname: Option, + pub new_nickname: Option, + } + /* #[derive(Debug, Default, Deserialize, Serialize, ToSchema)] pub struct GetContactResponse { @@ -195,4 +229,94 @@ pub mod endpoint { (axum::http::StatusCode::OK, axum::Json(response)) } + + /// Endpoint to update Contact + #[utoipa::path( + patch, + path = super::super::endpoints::UPDATE_CONTACT_NAME, + request_body( + content = super::request::UpdateContactNamesRequest, + description = "Data needed to update Contact names", + content_type = "application/json" + ), + responses( + (status = 201, description = "Contact created", body = super::response::UpdateContactNamesResponse), + (status = 400, description = "Error", body = super::response::UpdateContactNamesResponse), + (status = 500, description = "Error creating Contact", body = super::response::UpdateContactNamesResponse) + ) +)] + pub async fn update_contact_names( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, + ) -> ( + axum::http::StatusCode, + axum::Json, + ) { + let mut response = super::response::UpdateContactNamesResponse::default(); + + if payload.is_valid() { + match contact_repo::get(&pool, &payload.id).await { + Ok(contact) => { + let mut old_firstname: Option = contact.firstname.clone(); + let mut old_lastname: Option = contact.lastname.clone(); + let mut old_nickname: Option = contact.nickname.clone(); + let new_firstname = match payload.firstname { + Some(firstname) => Some(firstname), + None => None, + }; + let new_lastname = match payload.lastname { + Some(lastname) => Some(lastname), + None => None, + }; + let new_nickname = match payload.nickname { + Some(nickname) => Some(nickname), + None => None, + }; + match contact_repo::update_names( + &pool, + &payload.id, + &new_firstname, + &new_lastname, + &new_nickname, + ) + .await + { + Ok(()) => { + response.message = String::from(super::super::response::SUCCESSFUL); + let data = super::response::UpdateContactNameChange { + old_firstname, + old_lastname, + old_nickname, + new_firstname, + new_lastname, + new_nickname, + ..Default::default() + }; + response.data = vec![data]; + (axum::http::StatusCode::OK, axum::Json(response)) + } + Err(err) => { + eprintln!("Error: {err:?}"); + response.message = String::from("Something went wrong"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response), + ) + } + } + } + Err(err) => { + eprintln!("Error: {err:?}"); + response.message = String::from("Something went wrong"); + ( + 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 d0911be..e269cdf 100644 --- a/src/caller/mod.rs +++ b/src/caller/mod.rs @@ -7,6 +7,8 @@ pub mod endpoints { pub const ADD_CONTACT: &str = "/api/v1/contact/new"; /// Constant for getting Contact endpoint 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/new"; } pub mod response { diff --git a/src/config/mod.rs b/src/config/mod.rs index ebded9a..3ffa738 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -95,6 +95,12 @@ pub mod init { crate::auth::auth::, )), ) + .route( + crate::caller::endpoints::UPDATE_CONTACT_NAME, + get(contact_endpoints::update_contact_names).route_layer( + axum::middleware::from_fn(crate::auth::auth::), + ), + ) .layer(cors::configure_cors().await) } diff --git a/src/repo/contact.rs b/src/repo/contact.rs new file mode 100644 index 0000000..b8bd257 --- /dev/null +++ b/src/repo/contact.rs @@ -0,0 +1,201 @@ + +use sqlx::Row; + +pub async fn insert( + pool: &sqlx::PgPool, + contact: &textsender_models::contact::Contact, +) -> Result { + match sqlx::query( + r#" + INSERT INTO "contacts" (firstname, lastname, nickname, phone_number, user_id) + VALUES($1, $2, $3, $4, $5) RETURNING id; + "#, + ) + .bind(&contact.firstname) + .bind(&contact.lastname) + .bind(&contact.nickname) + .bind(&contact.phone_number) + .bind(contact.user_id) + .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, firstname, lastname, nickname, phone_number, user_id FROM "contacts" + WHERE + id = $1; + "#, + ) + .bind(id) + .fetch_one(pool) + .await + { + Ok(row) => { + let id: uuid::Uuid = row.try_get("id")?; + + let phone_number: String = row.try_get("phone_number")?; + let user_id: uuid::Uuid = row.try_get("user_id")?; + Ok(textsender_models::contact::Contact { + id: Some(id), + firstname: match row.try_get("firstname") { + Ok(val) => val, + Err(err) => { + eprintln!("Error: {err:?}"); + None + } + }, + lastname: match row.try_get("lastname") { + Ok(val) => val, + Err(err) => { + eprintln!("Error: {err:?}"); + None + } + }, + nickname: match row.try_get("nickname") { + Ok(val) => val, + Err(err) => { + eprintln!("Error: nickname has no value for contacts record {err:?}"); + None + } + }, + phone_number, + user_id: Some(user_id), + }) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_with_user_id_and_phone( + pool: &sqlx::PgPool, + user_id: &uuid::Uuid, + phone_number: &str, +) -> Result { + match sqlx::query( + r#" + SELECT id, firstname, lastname, nickname, phone_number, user_id FROM "contacts" + WHERE + phone_number = $1 AND user_id = $2; + "#, + ) + .bind(phone_number) + .bind(user_id) + .fetch_one(pool) + .await + { + Ok(row) => { + println!("In here"); + let id: uuid::Uuid = row.try_get("id")?; + let firstname: String = row.try_get("firstname")?; + let lastname: String = row.try_get("lastname")?; + let nickname: String = row.try_get("nickname")?; + let phone_number: String = row.try_get("phone_number")?; + let user_id: uuid::Uuid = row.try_get("user_id")?; + println!("Found"); + Ok(textsender_models::contact::Contact { + id: Some(id), + firstname: Some(firstname), + lastname: Some(lastname), + nickname: Some(nickname), + phone_number, + user_id: Some(user_id), + }) + } + Err(err) => match err { + sqlx::Error::RowNotFound => Err(err), + _ => Err(sqlx::Error::WorkerCrashed), + }, + } +} + +pub async fn get_with_user_id( + pool: &sqlx::PgPool, + user_id: &uuid::Uuid, +) -> Result, sqlx::Error> { + match sqlx::query( + r#" + SELECT id, firstname, lastname, nickname, phone_number, user_id FROM "contacts" + WHERE + user_id = $1; + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await + { + Ok(rows) => { + let mut contacts: Vec = Vec::new(); + for row in rows { + let id: uuid::Uuid = row.try_get("id")?; + + let phone_number: String = row.try_get("phone_number")?; + let user_id: uuid::Uuid = row.try_get("user_id")?; + let contact = textsender_models::contact::Contact { + id: Some(id), + firstname: match row.try_get("firstname") { + Ok(val) => val, + Err(err) => { + eprintln!("Error: {err:?}"); + None + } + }, + lastname: match row.try_get("lastname") { + Ok(val) => val, + Err(err) => { + eprintln!("Error: {err:?}"); + None + } + }, + nickname: match row.try_get("nickname") { + Ok(val) => val, + Err(err) => { + eprintln!("Error: nickname has no value for contacts record {err:?}"); + None + } + }, + phone_number, + user_id: Some(user_id), + }; + contacts.push(contact); + } + Ok(contacts) + } + Err(_) => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn update_names( + pool: &sqlx::PgPool, + id: &uuid::Uuid, + firstname: &Option, + lastname: &Option, + nickname: &Option, +) -> Result<(), sqlx::Error> { + match sqlx::query( + r#" + UPDATE "contacts" SET firstname = $1, lastname = $2, nickname = $3 WHERE id = $4; + "#, + ) + .bind(firstname) + .bind(lastname) + .bind(nickname) + .bind(id) + .execute(pool) + .await + { + Ok(_row) => Ok(()), + Err(_) => Err(sqlx::Error::RowNotFound), + } +} diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 6bda712..40d6353 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,180 +1 @@ -pub mod contact { - use sqlx::Row; - - pub async fn insert( - pool: &sqlx::PgPool, - contact: &textsender_models::contact::Contact, - ) -> Result { - match sqlx::query( - r#" - INSERT INTO "contacts" (firstname, lastname, nickname, phone_number, user_id) - VALUES($1, $2, $3, $4, $5) RETURNING id; - "#, - ) - .bind(&contact.firstname) - .bind(&contact.lastname) - .bind(&contact.nickname) - .bind(&contact.phone_number) - .bind(contact.user_id) - .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, firstname, lastname, nickname, phone_number, user_id FROM "contacts" - WHERE - id = $1; - "#, - ) - .bind(id) - .fetch_one(pool) - .await - { - Ok(row) => { - let id: uuid::Uuid = row.try_get("id")?; - - let phone_number: String = row.try_get("phone_number")?; - let user_id: uuid::Uuid = row.try_get("user_id")?; - Ok(textsender_models::contact::Contact { - id: Some(id), - firstname: match row.try_get("firstname") { - Ok(val) => val, - Err(err) => { - eprintln!("Error: {err:?}"); - None - } - }, - lastname: match row.try_get("lastname") { - Ok(val) => val, - Err(err) => { - eprintln!("Error: {err:?}"); - None - } - }, - nickname: match row.try_get("nickname") { - Ok(val) => val, - Err(err) => { - eprintln!("Error: nickname has no value for contacts record {err:?}"); - None - } - }, - phone_number, - user_id: Some(user_id), - }) - } - Err(_) => Err(sqlx::Error::RowNotFound), - } - } - - pub async fn get_with_user_id_and_phone( - pool: &sqlx::PgPool, - user_id: &uuid::Uuid, - phone_number: &str, - ) -> Result { - match sqlx::query( - r#" - SELECT id, firstname, lastname, nickname, phone_number, user_id FROM "contacts" - WHERE - phone_number = $1 AND user_id = $2; - "#, - ) - .bind(phone_number) - .bind(user_id) - .fetch_one(pool) - .await - { - Ok(row) => { - println!("In here"); - let id: uuid::Uuid = row.try_get("id")?; - let firstname: String = row.try_get("firstname")?; - let lastname: String = row.try_get("lastname")?; - let nickname: String = row.try_get("nickname")?; - let phone_number: String = row.try_get("phone_number")?; - let user_id: uuid::Uuid = row.try_get("user_id")?; - println!("Found"); - Ok(textsender_models::contact::Contact { - id: Some(id), - firstname: Some(firstname), - lastname: Some(lastname), - nickname: Some(nickname), - phone_number, - user_id: Some(user_id), - }) - } - Err(err) => match err { - sqlx::Error::RowNotFound => Err(err), - _ => Err(sqlx::Error::WorkerCrashed), - }, - } - } - - pub async fn get_with_user_id( - pool: &sqlx::PgPool, - user_id: &uuid::Uuid, - ) -> Result, sqlx::Error> { - match sqlx::query( - r#" - SELECT id, firstname, lastname, nickname, phone_number, user_id FROM "contacts" - WHERE - user_id = $1; - "#, - ) - .bind(user_id) - .fetch_all(pool) - .await - { - Ok(rows) => { - let mut contacts: Vec = Vec::new(); - for row in rows { - let id: uuid::Uuid = row.try_get("id")?; - - let phone_number: String = row.try_get("phone_number")?; - let user_id: uuid::Uuid = row.try_get("user_id")?; - let contact = textsender_models::contact::Contact { - id: Some(id), - firstname: match row.try_get("firstname") { - Ok(val) => val, - Err(err) => { - eprintln!("Error: {err:?}"); - None - } - }, - lastname: match row.try_get("lastname") { - Ok(val) => val, - Err(err) => { - eprintln!("Error: {err:?}"); - None - } - }, - nickname: match row.try_get("nickname") { - Ok(val) => val, - Err(err) => { - eprintln!( - "Error: nickname has no value for contacts record {err:?}" - ); - None - } - }, - phone_number, - user_id: Some(user_id), - }; - contacts.push(contact); - } - Ok(contacts) - } - Err(_) => Err(sqlx::Error::RowNotFound), - } - } -} +pub mod contact;