From 132906b548fbf4ea6e6ed53e4bf4176ab37f1aa0 Mon Sep 17 00:00:00 2001 From: David Westgate Date: Thu, 30 May 2024 15:46:22 -0700 Subject: [PATCH] implement tag DB functions --- src/answer.rs | 39 ++++++++++++------------------ src/api.rs | 27 ++++++++++----------- src/pg_store.rs | 64 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 79 insertions(+), 51 deletions(-) diff --git a/src/answer.rs b/src/answer.rs index 6d118a4..0aceaec 100644 --- a/src/answer.rs +++ b/src/answer.rs @@ -2,47 +2,38 @@ use crate::*; #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Answer { - content: String, - question_id: u8, +pub struct NewAnswer { + pub content: String, + pub question_id: u8, } -impl Answer { - pub fn _new(_id: u8, content: String, question_id: u8) -> Self { - Answer { +impl NewAnswer { + pub fn _new(content: String, question_id: u8) -> Self { + NewAnswer { content, question_id, } } - pub fn to_dto(&self, id: u8) -> AnswerDTO { - AnswerDTO { - id, - content: self.content.clone(), - question_id: self.question_id, - } - } } #[derive(Deserialize, Serialize, Clone, Debug)] -pub struct AnswerDTO { +pub struct Answer { id: u8, content: String, question_id: u8, } /// Answer Data Transfer Object, a representation of the expected serialized JSON formated of answer regarding requests, responses, and our answer json file -impl AnswerDTO { - pub fn to_entity(&self) -> (u8, Answer) { - ( - self.id, - Answer { - content: self.content.clone(), - question_id: self.question_id, - }, - ) +impl Answer { + pub fn new(id: u8, content: String, question_id: u8) -> Self { + Answer { + id, + content, + question_id, + } } } -impl IntoResponse for &AnswerDTO { +impl IntoResponse for &Answer { fn into_response(self) -> Response { (StatusCode::OK, Json(&self)).into_response() } diff --git a/src/api.rs b/src/api.rs index fbdb131..9c1ccb0 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,6 +1,6 @@ /// All API route handlers of the application use self::{ - answer::AnswerDTO, + answer::NewAnswer, pg_store::Store, question::{NewQuestion, Question, QuestionDTO}, }; @@ -99,19 +99,19 @@ pub async fn create_question( /// Http Not Found 404 and a message are returned if a question with that `id` does not already exist /// Http Unprocessable Entity 422 is returned (implicitlly) for a malformed body pub async fn update_question( - State(store): State>>, - Json(question): Json, + State(_store): State>>, + Json(_question): Json, ) -> Response { // Step 0: Update the question entry // Step 1: Fetch the question_tags for the given tags // Step 2: Fetch the tags for the given question tags // Step 3: new_tags_labels = list of tag labels not already on question - // Step 3a: Fetch existing_new_tag as tags which already by given name exist, but do not yet have a question_tag association - // Step 3b: Create question_tag association for 3a - // Step 3c: Create new tags which do not already exist - // Step 3d: Create question_tag association for 3c + // Step 3a: Fetch existing_new_tag as tags which already by given name exist, but do not yet have a question_tag association + // Step 3b: Create question_tag association for 3a + // Step 3c: Create new tags which do not already exist + // Step 3d: Create question_tag association for 3c // Step 4: remove_tag_labls = list of tag labs which should no longer have a question_tag association with the question - // Step 4a: Fetch existing_old_tags as tags by given na + // Step 4a: Fetch existing_old_tags as tags by given na // match store.write().await.update_question(question) { // Ok(question) => question.into_response(), @@ -130,8 +130,8 @@ pub async fn delete_question( State(store): State>>, Path(id): Path, ) -> Response { - match store.write().await.remove_question(id) { - Ok(question) => question.into_response(), + match store.write().await.remove_question(id).await { + Ok(_) => (StatusCode::OK, format!("Question with id {} deleted", id)).into_response(), Err(e) => (StatusCode::NOT_FOUND, e).into_response(), } } @@ -145,11 +145,10 @@ pub async fn delete_question( /// Status Unprocessable Entity 422 is returned (implicitlly) for a malformed body pub async fn create_answer( State(store): State>>, - Form(answer_dto): Form, + Form(new_answer): Form, ) -> Response { - let (id, answer) = answer_dto.to_entity(); - match store.write().await.add_answer(id, answer) { - Ok(answer) => (StatusCode::CREATED, Json(&answer.to_dto(id))).into_response(), + match store.write().await.add_answer(new_answer).await { + Ok(answer) => (StatusCode::CREATED, Json(answer)).into_response(), Err(e) => (StatusCode::CONFLICT, e).into_response(), } } diff --git a/src/pg_store.rs b/src/pg_store.rs index f786c2e..ce64b59 100644 --- a/src/pg_store.rs +++ b/src/pg_store.rs @@ -1,3 +1,5 @@ +use answer::NewAnswer; + /// Store is responsible for manageing the in-memory hashmap of questions by providing initialization read/write functions, /// and file I/O operations to persist these questions /// TODO - Results returning errors should use specified types, not strings @@ -125,8 +127,15 @@ impl Store { } } - pub fn remove_question(&mut self, _id: u8) -> Result { - Err("To Do".to_string()) + pub async fn remove_question(&mut self, id: u8) -> Result { + let result = sqlx::query("DELETE FROM questions WHERE id = $1") + .bind(id.to_string()) + .execute(&self.connection) + .await; + match result { + Ok(_) => Ok(true), + Err(e) => Err(e.to_string()), + } } pub async fn fetch_one_question_by_id(&self, id: u8) -> Option { @@ -169,28 +178,57 @@ impl Store { /// Remove tags from tags table, which have no question tag association /// Returns true if any tags were removed, false otherwise - pub fn remove_orphan_tags(&mut self){ - todo!() + pub async fn _remove_orphan_tags(&mut self) -> Result { + let result = sqlx::query( + "DELETE FROM tags where id NOT IN (SELECT DISTINCT tag_id from question_tag)", + ) + .execute(&self.connection) + .await; + match result { + Ok(_) => Ok(true), + Err(e) => Err(e.to_string()), + } } /// Creates passed in tag labels in the tags table, only if they do not already exists /// Returns list of all tags from passed in labels, regardless of if they already existed - pub fn add_tags_if_not_exists(&mut self, tags_labels: Vec){ + pub fn _add_tags_if_not_exists(&mut self, _tags_labels: Vec) { todo!() } - /// - pub fn sync_question_tags(&mut self, tags: Vec){ + /// + pub fn _sync_question_tags(&mut self, _tags: Vec) { todo!() } - - - pub fn update_question(&mut self, _question: Question) -> Result { - todo!() + pub async fn _update_question(&mut self, question: Question) -> Result { + let result = sqlx::query("UPDATE questions SET title = $1 AND SET content = $2 WHERE id = $3 RETURNING id, title, content") + .bind(question.title).bind(question.content).bind(question.id.to_string()) + .fetch_one(&self.connection).await; + match result { + Ok(pg_row) => Ok(Question::new( + Store::id_to_u8(&pg_row, "id"), + pg_row.get("title"), + pg_row.get("content"), + )), + Err(e) => Err(e.to_string()), + } } - pub fn add_answer(&mut self, _id: u8, _answer: Answer) -> Result { - todo!() + pub async fn add_answer(&mut self, new_answer: NewAnswer) -> Result { + let result = + sqlx::query("INSERT INTO answers VALUES ($1,$2) RETURNING id, content, question_id") + .bind(new_answer.content) + .bind(new_answer.question_id.to_string()) + .fetch_one(&self.connection) + .await; + match result { + Ok(pg_row) => Ok(Answer::new( + Store::id_to_u8(&pg_row, "id"), + pg_row.get("content"), + Store::id_to_u8(&pg_row, "question_id"), + )), + Err(e) => Err(e.to_string()), + } } }