185 lines
7.3 KiB
Rust
185 lines
7.3 KiB
Rust
/// All API route handlers of the application
|
|
use self::{
|
|
answer::NewAnswer,
|
|
pg_store::Store,
|
|
question::{NewQuestion, QuestionDTO},
|
|
};
|
|
use crate::*;
|
|
const DEFAULT_PAGE: usize = 0;
|
|
const DEFAULT_PAGE_SIZE: usize = 10;
|
|
|
|
/// Fetches a single question using a provided id. First we fetch the question, then the questions tags, then build that into a response DTO
|
|
/// # Parameters
|
|
/// `id`: Path parmater Id of the question to lookup
|
|
/// # Returns
|
|
/// If the question is found, it converts the question to its Data Transfer Object (DTO) format and returns it.
|
|
/// If the question is not found, it returns a 404 NOT_FOUND response with the error message.
|
|
pub async fn read_question(
|
|
State(store): State<Arc<RwLock<Store>>>,
|
|
Path(id): Path<u8>,
|
|
) -> Response {
|
|
//First, fetch the question
|
|
let question_result = store.read().await.fetch_one_question_by_id(id).await;
|
|
match question_result {
|
|
Some(question) => {
|
|
//Then fetch the tags for this question, if there are any
|
|
let tags_result = store.read().await.get_tags_for_question(question.id).await;
|
|
match tags_result {
|
|
Some(tags) => QuestionDTO::new(question, tags).into_response(),
|
|
None => QuestionDTO::new(question, vec![]).into_response(),
|
|
}
|
|
}
|
|
None => (StatusCode::NOT_EXTENDED).into_response(),
|
|
}
|
|
}
|
|
|
|
/// Fetches all questions
|
|
/// Load the questions, then load the tags for each question and build the json response
|
|
/// # Parameters
|
|
/// 'page' and 'size' as query paramateres
|
|
/// # Returns
|
|
/// A list of questions, empty if none exist
|
|
pub async fn read_questions(
|
|
State(store): State<Arc<RwLock<Store>>>,
|
|
Query(pagination): Query<Pagination>,
|
|
) -> Response {
|
|
let page: usize = pagination.page.unwrap_or(DEFAULT_PAGE);
|
|
let size: usize = pagination.size.unwrap_or(DEFAULT_PAGE_SIZE);
|
|
let start: usize = page * size;
|
|
let questions_option = store.read().await.fetch_many_questions(start, size).await;
|
|
match questions_option {
|
|
Some(questions) => {
|
|
let mut response_vec_dto: Vec<QuestionDTO> = vec![];
|
|
for question in questions {
|
|
//Not ideal - hitting the database serially for the tags of each invidual question. Can be optimized with more complex sql
|
|
let tags = store
|
|
.read()
|
|
.await
|
|
.fetch_tags_by_property(question.id, "question_id")
|
|
.await
|
|
.unwrap_or(vec![]);
|
|
let question_dto: QuestionDTO = QuestionDTO::new(question, tags);
|
|
response_vec_dto.push(question_dto)
|
|
}
|
|
(StatusCode::OK, Json(response_vec_dto)).into_response()
|
|
}
|
|
None => StatusCode::NO_CONTENT.into_response(),
|
|
}
|
|
}
|
|
|
|
/// Creates a new question
|
|
/// # Parameters
|
|
/// `QuestionDTO` A JSON representation of a Question including its id
|
|
/// # Returns
|
|
/// Http Created 201 and the newly created question are return upon success
|
|
/// Http Confilct 409 and a message are returned if a question with that `id`` already exists
|
|
/// Http Unprocessable Entity 422 is returned (implicitlly) for a malformed body
|
|
pub async fn create_question(
|
|
State(store): State<Arc<RwLock<Store>>>,
|
|
Json(new_question): Json<NewQuestion>,
|
|
) -> Response {
|
|
match store.write().await.add_question(new_question).await {
|
|
Ok(question) => (StatusCode::CREATED, Json(&question)).into_response(),
|
|
Err(e) => (StatusCode::CONFLICT, e).into_response(),
|
|
}
|
|
}
|
|
|
|
/// Updates an existing question
|
|
/// TODO: Better handle errors, responses in the tag updating flow - it is getting complex
|
|
/// # Parameters
|
|
/// `QuestionDTO` A JSON representation of a Question including its id
|
|
/// # Returns
|
|
/// Http Ok 200 and the updated question are return upon success
|
|
/// 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<Arc<RwLock<Store>>>,
|
|
Json(question): Json<QuestionDTO>,
|
|
) -> Response {
|
|
// 1: Update the question entity
|
|
let updated_question_result = store
|
|
.write()
|
|
.await
|
|
.update_question(question.id, question.title, question.content)
|
|
.await;
|
|
match updated_question_result {
|
|
Ok(updated_question) => {
|
|
// 2: Get the list of tags currently associated with this question
|
|
let current_tags_option = store
|
|
.read()
|
|
.await
|
|
.get_tags_for_question(updated_question.id)
|
|
.await;
|
|
match current_tags_option {
|
|
Some(current_tags) => {
|
|
let incoming_tag_labels = question.tags;
|
|
// Create (ignored if they already exist, new tags for the incoming tags)
|
|
let incoming_tags = store
|
|
.write()
|
|
.await
|
|
.add_tags(incoming_tag_labels)
|
|
.await
|
|
.unwrap();
|
|
// Unassociated all current tags with this question
|
|
let _remove_tags = store
|
|
.write()
|
|
.await
|
|
.unassociate_tags(updated_question.id, current_tags)
|
|
.await
|
|
.unwrap();
|
|
// Associated all of the incoming tags (now newly created if needed) with the question
|
|
let _updated_tags = store
|
|
.write()
|
|
.await
|
|
.associate_tags(updated_question.id, &incoming_tags)
|
|
.await
|
|
.unwrap();
|
|
//Return the updated question with the updated tags
|
|
QuestionDTO::new(updated_question, incoming_tags).into_response()
|
|
}
|
|
None => (StatusCode::OK).into_response(),
|
|
}
|
|
}
|
|
Err(e) => (StatusCode::CONFLICT, e).into_response(),
|
|
}
|
|
}
|
|
|
|
/// Delete an existing question
|
|
/// # Parameters
|
|
/// `id`: Path parmater Id of the question to delete
|
|
/// # Returns
|
|
/// Http Ok 200 and the deleted question are return upon success
|
|
/// Http Not Found 404 and a message are returned if a question with that `id` does not exist
|
|
pub async fn delete_question(
|
|
State(store): State<Arc<RwLock<Store>>>,
|
|
Path(id): Path<u8>,
|
|
) -> 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(),
|
|
}
|
|
}
|
|
|
|
/// Create an Answer
|
|
/// # Parameters
|
|
/// `answer_dto` Form URL encoded answer DTO
|
|
/// # Returns
|
|
/// Status Created 201 and the created answer upon success
|
|
/// Status Conflict 409 and message if answer with ID already exists
|
|
/// Status Unprocessable Entity 422 is returned (implicitlly) for a malformed body
|
|
pub async fn create_answer(
|
|
State(store): State<Arc<RwLock<Store>>>,
|
|
Form(new_answer): Form<NewAnswer>,
|
|
) -> 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(),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct Pagination {
|
|
page: Option<usize>,
|
|
size: Option<usize>,
|
|
}
|