fix add/fetch all questions
This commit is contained in:
parent
036f4c4779
commit
094496961a
@ -15,7 +15,9 @@ CREATE TABLE IF NOT EXISTS tags (
|
||||
CREATE TABLE IF NOT EXISTS question_tag (
|
||||
question_id INT REFERENCES questions(id),
|
||||
tag_id INT REFERENCES tags(id),
|
||||
PRIMARY KEY (question_id, tag_id)
|
||||
PRIMARY KEY (question_id, tag_id),
|
||||
FOREIGN KEY (question_id) REFERENCES questions(id),
|
||||
FOREIGN KEY (tag_id) REFERENCES tags(id)
|
||||
);
|
||||
|
||||
-- Create answers table
|
||||
|
36
src/api.rs
36
src/api.rs
@ -5,8 +5,9 @@ use self::{
|
||||
question::{NewQuestion, QuestionDTO},
|
||||
};
|
||||
use crate::*;
|
||||
const DEFAULT_PAGE: usize = 0;
|
||||
const DEFAULT_PAGE_SIZE: usize = 10;
|
||||
// sqlx wants 'bigint' = i32
|
||||
const DEFAULT_PAGE: i32 = 0;
|
||||
const DEFAULT_PAGE_SIZE: i32 = 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
|
||||
@ -43,23 +44,28 @@ 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 page: i32 = pagination.page.unwrap_or(DEFAULT_PAGE);
|
||||
let size: i32 = pagination.size.unwrap_or(DEFAULT_PAGE_SIZE);
|
||||
let start: i32 = page * size;
|
||||
let questions_option = store.read().await.fetch_many_questions(start, size).await;
|
||||
match questions_option {
|
||||
Some(questions) => {
|
||||
println!("Num question {}", questions.len());
|
||||
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)
|
||||
let tags_option = store.read().await.get_tags_for_question(question.id).await;
|
||||
match tags_option {
|
||||
Some(tags) => {
|
||||
println!("tags {:?}", tags);
|
||||
let question_dto: QuestionDTO = QuestionDTO::new(question, tags);
|
||||
response_vec_dto.push(question_dto)
|
||||
}
|
||||
None => {
|
||||
let question_dto: QuestionDTO = QuestionDTO::new(question, vec![]);
|
||||
response_vec_dto.push(question_dto)
|
||||
}
|
||||
}
|
||||
}
|
||||
(StatusCode::OK, Json(response_vec_dto)).into_response()
|
||||
}
|
||||
@ -179,6 +185,6 @@ pub async fn create_answer(
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Pagination {
|
||||
page: Option<usize>,
|
||||
size: Option<usize>,
|
||||
page: Option<i32>,
|
||||
size: Option<i32>,
|
||||
}
|
||||
|
116
src/pg_store.rs
116
src/pg_store.rs
@ -29,7 +29,7 @@ impl Store {
|
||||
.await
|
||||
{
|
||||
Ok(pool) => pool,
|
||||
Err(_) => panic!("Could not estable database connection"),
|
||||
Err(_) => panic!("Could not establish database connection"),
|
||||
};
|
||||
sqlx::migrate!().run(&db_pool).await.unwrap();
|
||||
Store {
|
||||
@ -39,9 +39,12 @@ impl Store {
|
||||
|
||||
//Get the question tags for a particular question
|
||||
pub async fn get_tags_for_question(&self, question_id: u8) -> Option<Vec<Tag>> {
|
||||
let query = "SELECT label FROM tag JOIN question_tag on tag.id = question_tag.tag_id JOIN question on tag.question_id = $1";
|
||||
let query = "SELECT tags.id, tags.label FROM tags
|
||||
JOIN question_tag on tags.id = question_tag.tag_id
|
||||
JOIN questions on question_tag.question_id = questions.id
|
||||
WHERE questions.id = ($1)";
|
||||
let result = sqlx::query(query)
|
||||
.bind(question_id.to_string())
|
||||
.bind(i32::from(question_id))
|
||||
.fetch_all(&self.connection)
|
||||
.await;
|
||||
match result {
|
||||
@ -52,7 +55,10 @@ impl Store {
|
||||
.collect::<Vec<Tag>>();
|
||||
Some(tags)
|
||||
}
|
||||
_ => None,
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,47 +103,68 @@ impl Store {
|
||||
// Add new tags to tags table, only if tags with existing label do not exist. If they already exist, just ignore that
|
||||
// Returns list of tags
|
||||
pub async fn add_tags(&mut self, tag_labels: Vec<String>) -> Result<Vec<Tag>, String> {
|
||||
let insert_values = tag_labels.join(",");
|
||||
let query =
|
||||
"INSERT INTO tags (label) VALUES ($1) ON CONFLICT (label) DO NOTHING RETURNING *";
|
||||
let result = sqlx::query(query)
|
||||
.bind(insert_values)
|
||||
.fetch_all(&self.connection)
|
||||
let insert_query = "
|
||||
INSERT INTO tags (label)
|
||||
SELECT * FROM UNNEST(($1)::text[]) AS label
|
||||
ON CONFLICT (label) DO NOTHING
|
||||
";
|
||||
|
||||
//First run the insert query on the new labels
|
||||
let insert_result = sqlx::query(insert_query)
|
||||
.bind(&tag_labels)
|
||||
.execute(&self.connection)
|
||||
.await;
|
||||
match result {
|
||||
Ok(pg_rows) => {
|
||||
let tags: Vec<Tag> = pg_rows
|
||||
.iter()
|
||||
.map(|pg_row| Tag::new(Store::id_to_u8(pg_row, "id"), pg_row.get("label")))
|
||||
.collect();
|
||||
Ok(tags)
|
||||
match insert_result {
|
||||
Ok(_) => {
|
||||
//Then run the select query on the new labels (which may include already existing labels)
|
||||
let select_query = "
|
||||
SELECT id, label FROM tags
|
||||
WHERE label = ANY($1::text[]);
|
||||
";
|
||||
match sqlx::query(select_query)
|
||||
.bind(&tag_labels)
|
||||
.fetch_all(&self.connection)
|
||||
.await
|
||||
{
|
||||
Ok(pg_rows) => {
|
||||
let tags: Vec<Tag> = pg_rows
|
||||
.iter()
|
||||
.map(|pg_row| {
|
||||
Tag::new(Store::id_to_u8(pg_row, "id"), pg_row.get("label"))
|
||||
})
|
||||
.collect();
|
||||
Ok(tags)
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
//Takes a question id and list of tags, and creates question_tag join associations
|
||||
//Ignores if association already exists
|
||||
//Returns list of the question tag associations
|
||||
/// Takes a question id and list of tags, and creates question_tag join associations
|
||||
/// Ignores if association already exists
|
||||
/// Returns list of the question tag associations
|
||||
pub async fn associate_tags(
|
||||
&mut self,
|
||||
question_id: u8,
|
||||
tags: &[Tag],
|
||||
) -> Result<Vec<QuestionTag>, String> {
|
||||
//for the query binding we must convert the tag vector to a comma seperated string list of questionid/tagid tuples
|
||||
let question_id_tag_id_tuple_string = tags
|
||||
.iter()
|
||||
.map(|tag| format!("({},{})", question_id, tag.id))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
let tag_ids_string: Vec<String> = tags.iter().map(|tag| tag.id.to_string()).collect();
|
||||
|
||||
let query = "INSERT INTO question_tag VALUES ($1) ON CONFLICT (question_id, tag_id) DO NOTHING RETURNING *";
|
||||
let query = "
|
||||
INSERT INTO question_tag (question_id, tag_id)
|
||||
SELECT $1::smallint, UNNEST($2::smallint[])
|
||||
ON CONFLICT DO NOTHING;
|
||||
";
|
||||
let result = sqlx::query(query)
|
||||
.bind(question_id_tag_id_tuple_string)
|
||||
.bind(question_id.to_string())
|
||||
.bind(tag_ids_string)
|
||||
.fetch_all(&self.connection)
|
||||
.await;
|
||||
match result {
|
||||
Ok(pg_rows) => {
|
||||
println!("Num rows {}", pg_rows.len());
|
||||
let question_tags: Vec<QuestionTag> = pg_rows
|
||||
.iter()
|
||||
.map(|pg_row| {
|
||||
@ -178,25 +205,32 @@ impl Store {
|
||||
}
|
||||
|
||||
// Fetch a list of tags by either the tag id, or by the label. Up to the caller
|
||||
pub async fn fetch_tags_by_property(
|
||||
pub async fn _fetch_tags_by_property(
|
||||
&self,
|
||||
propert_id: u8,
|
||||
property_type: &str,
|
||||
) -> Option<Vec<Tag>> {
|
||||
let result = sqlx::query("SELECT * FROM tags WHERE $1 = $2")
|
||||
.bind(property_type)
|
||||
println!("Property type {}", property_type);
|
||||
println!("Property id {} ", propert_id);
|
||||
let query = format!("SELECT * FROM tags WHERE {} = ($2);", property_type).to_string(); //looks risky, but user does not get to control property type
|
||||
let result = sqlx::query(&query)
|
||||
.bind(property_type.to_string())
|
||||
.bind(propert_id.to_string())
|
||||
.fetch_all(&self.connection)
|
||||
.await;
|
||||
match result {
|
||||
Ok(tag_rows) => {
|
||||
let tags: Vec<Tag> = tag_rows
|
||||
Ok(pg_rows) => {
|
||||
println!("num tag rows {}", pg_rows.len());
|
||||
let tags: Vec<Tag> = pg_rows
|
||||
.iter()
|
||||
.map(|pg_row| Tag::new(Store::id_to_u8(pg_row, "id"), pg_row.get("label")))
|
||||
.collect();
|
||||
Some(tags)
|
||||
}
|
||||
_ => None,
|
||||
Err(e) => {
|
||||
println!("err {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,15 +326,17 @@ impl Store {
|
||||
}
|
||||
|
||||
// Fetch many questions - do not worry about joining the tags
|
||||
pub async fn fetch_many_questions(&self, start: usize, size: usize) -> Option<Vec<Question>> {
|
||||
pub async fn fetch_many_questions(&self, start: i32, size: i32) -> Option<Vec<Question>> {
|
||||
let rows_result: Result<Vec<PgRow>, sqlx::Error> =
|
||||
sqlx::query("SELECT * FROM questions ORDER BY id LIMIT $1 OFFSET $2")
|
||||
.bind(size.to_string())
|
||||
.bind(start.to_string())
|
||||
.bind(size)
|
||||
.bind(start)
|
||||
.fetch_all(&self.connection)
|
||||
.await;
|
||||
|
||||
match rows_result {
|
||||
Ok(pg_rows) => {
|
||||
println!("num rows {}", pg_rows.len());
|
||||
let mut result: Vec<Question> = vec![];
|
||||
for pg_row in pg_rows {
|
||||
result.push(Question::new(
|
||||
@ -311,7 +347,10 @@ impl Store {
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
_ => None,
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,6 +388,7 @@ impl Store {
|
||||
}
|
||||
}
|
||||
|
||||
// Add an answer entity
|
||||
pub async fn add_answer(&mut self, new_answer: NewAnswer) -> Result<Answer, String> {
|
||||
let result =
|
||||
sqlx::query("INSERT INTO answers VALUES ($1,$2) RETURNING id, content, question_id")
|
||||
|
Reference in New Issue
Block a user