1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2024-12-28 14:38:52 +00:00

Add survey posts support

This commit is contained in:
Pierre HUBERT 2020-07-04 16:44:42 +02:00
parent 1e956cdcb5
commit 8d5013b00a
12 changed files with 237 additions and 13 deletions

View File

@ -31,4 +31,6 @@ pub mod group_member_api;
pub mod friend_api; pub mod friend_api;
pub mod friendship_status_api; pub mod friendship_status_api;
pub mod post_api; pub mod post_api;
pub mod movie_api; pub mod movie_api;
pub mod survey_choice_api;
pub mod survey_api;

View File

@ -4,10 +4,11 @@
use serde::Serialize; use serde::Serialize;
use crate::api_data::movie_api::MovieAPI; use crate::api_data::movie_api::MovieAPI;
use crate::api_data::survey_api::SurveyAPI;
use crate::data::error::ResultBoxError; use crate::data::error::ResultBoxError;
use crate::data::post::{Post, PostKind}; use crate::data::post::{Post, PostKind};
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::helpers::movies_helper; use crate::helpers::{movies_helper, survey_helper};
use crate::utils::user_data_utils::user_data_url; use crate::utils::user_data_utils::user_data_url;
#[derive(Serialize)] #[derive(Serialize)]
@ -40,11 +41,14 @@ pub struct PostAPI {
// Countdown timer specific // Countdown timer specific
time_end: Option<u64>, time_end: Option<u64>,
// Survey specific
data_survey: Option<SurveyAPI>,
} }
impl PostAPI { impl PostAPI {
/// Turn a `Post` entry into an API entry /// Turn a `Post` entry into an API entry
pub fn new(p: &Post) -> ResultBoxError<PostAPI> { pub fn new(p: &Post, user: &Option<UserID>) -> ResultBoxError<PostAPI> {
let mut post = PostAPI { let mut post = PostAPI {
ID: p.id, ID: p.id,
userID: p.user_id.id(), userID: p.user_id.id(),
@ -73,6 +77,9 @@ impl PostAPI {
// Countdown timer-specific // Countdown timer-specific
time_end: None, time_end: None,
// Survey specific
data_survey: None,
}; };
match &p.kind { match &p.kind {
@ -99,7 +106,9 @@ impl PostAPI {
PostKind::POST_KIND_COUNTDOWN(time_end) => post.time_end = Some(*time_end), PostKind::POST_KIND_COUNTDOWN(time_end) => post.time_end = Some(*time_end),
PostKind::POST_KIND_SURVEY => {} PostKind::POST_KIND_SURVEY =>
post.data_survey = Some(SurveyAPI::new(&survey_helper::get_info(p.id)?, user.clone())?),
PostKind::POST_KIND_YOUTUBE => {} PostKind::POST_KIND_YOUTUBE => {}
} }
@ -107,7 +116,7 @@ impl PostAPI {
} }
/// Turn a list of posts into an API entry /// Turn a list of posts into an API entry
pub fn for_list(l: &Vec<Post>) -> ResultBoxError<Vec<PostAPI>> { pub fn for_list(l: &Vec<Post>, user_id: Option<UserID>) -> ResultBoxError<Vec<PostAPI>> {
l.iter().map(Self::new).collect() l.iter().map(|p| Self::new(p, &user_id)).collect()
} }
} }

View File

@ -0,0 +1,47 @@
//! # Survey API information
//!
//! @author Pierre Hubert
use std::collections::HashMap;
use serde::Serialize;
use crate::api_data::survey_choice_api::SurveyChoiceAPI;
use crate::data::error::ResultBoxError;
use crate::data::survey::Survey;
use crate::data::user::UserID;
use crate::helpers::survey_helper;
#[derive(Serialize)]
#[allow(non_snake_case)]
pub struct SurveyAPI {
ID: u64,
userID: u64,
postID: u64,
creation_time: u64,
question: String,
user_choice: u64,
choices: HashMap<u64, SurveyChoiceAPI>,
allowNewChoices: bool,
}
impl SurveyAPI {
/// Create a new survey API entry
pub fn new(s: &Survey, curr_user_id: Option<UserID>) -> ResultBoxError<SurveyAPI> {
let user_choice = match &curr_user_id {
None => 0, /* -1 is not possible */
Some(user_id) => survey_helper::get_user_choice(s.id, user_id)?,
};
Ok(SurveyAPI {
ID: s.id,
userID: s.user_id.id(),
postID: s.post_id,
creation_time: s.time_create,
question: s.question.clone(),
user_choice,
choices: SurveyChoiceAPI::for_list(&s.choices),
allowNewChoices: s.allow_new_choices,
})
}
}

View File

@ -0,0 +1,33 @@
//! # Survey choice API
//!
//! @author Pierre Hubert
use std::collections::HashMap;
use serde::Serialize;
use crate::data::survey::SurveyChoice;
#[derive(Serialize)]
#[allow(non_snake_case)]
pub struct SurveyChoiceAPI {
choiceID: u64,
name: String,
responses: u64,
}
impl SurveyChoiceAPI {
pub fn new(c: &SurveyChoice) -> SurveyChoiceAPI {
SurveyChoiceAPI {
choiceID: c.id,
name: c.name.clone(),
responses: c.count,
}
}
pub fn for_list(c: &Vec<SurveyChoice>) -> HashMap<u64, SurveyChoiceAPI> {
let mut map = HashMap::with_capacity(c.len());
c.iter().for_each(|c| { map.insert(c.id, Self::new(c)); });
map
}
}

View File

@ -40,6 +40,15 @@ pub mod database_tables_names {
/// Movies table /// Movies table
pub const MOVIES_TABLE: &str = "galerie_video"; pub const MOVIES_TABLE: &str = "galerie_video";
/// Survey info table
pub const SURVEY_INFO_TABLE: &str = "sondage";
/// Survey choices table
pub const SURVEY_CHOICES_TABLE: &str = "sondage_choix";
/// Survey responses table
pub const SURVEY_RESPONSE_TABLE: &str = "sondage_reponse";
} }
/// The account image to show for user who do not have any /// The account image to show for user who do not have any

View File

@ -20,5 +20,5 @@ pub fn get_list_user(r: &mut HttpRequestHandler) -> RequestResult {
.set_start_from(start_from) .set_start_from(start_from)
.get_user(&user_id)?; .get_user(&user_id)?;
r.set_response(PostAPI::for_list(&posts)?) r.set_response(PostAPI::for_list(&posts, r.user_id_opt())?)
} }

View File

@ -20,4 +20,5 @@ pub mod global_search_result;
pub mod friend; pub mod friend;
pub mod friendship_status; pub mod friendship_status;
pub mod post; pub mod post;
pub mod movie; pub mod movie;
pub mod survey;

23
src/data/survey.rs Normal file
View File

@ -0,0 +1,23 @@
//! # Survey
//!
//! This structure contains all the information about a survey
//!
//! @author Pierre Hubert
use crate::data::user::UserID;
pub struct SurveyChoice {
pub id: u64,
pub name: String,
pub count: u64,
}
pub struct Survey {
pub id: u64,
pub user_id: UserID,
pub time_create: u64,
pub post_id: u64,
pub question: String,
pub choices: Vec<SurveyChoice>,
pub allow_new_choices: bool,
}

View File

@ -51,6 +51,13 @@ pub fn get_connection() -> Result<mysql::PooledConn, Box<dyn Error>> {
Ok(pool.get_conn()?) Ok(pool.get_conn()?)
} }
/// Join type
#[derive(PartialEq)]
pub enum DatabaseQueryJoinType {
NORMAL,
LEFT,
}
/// Structure used to implement JOIN on queries /// Structure used to implement JOIN on queries
struct QueryJoin { struct QueryJoin {
table: String, table: String,
@ -64,6 +71,7 @@ pub struct QueryInfo {
pub table_alias: Option<String>, pub table_alias: Option<String>,
/// Joins /// Joins
joins_type: DatabaseQueryJoinType,
joins: Vec<QueryJoin>, joins: Vec<QueryJoin>,
/// Query limits /// Query limits
@ -75,6 +83,9 @@ pub struct QueryInfo {
/// Custom WHERE values /// Custom WHERE values
pub custom_where_ars: Vec<mysql::Value>, pub custom_where_ars: Vec<mysql::Value>,
/// Custom GROUP BY argument
group_by: Option<String>,
/// Limit of the query (0 = no limit) /// Limit of the query (0 = no limit)
pub limit: u64, pub limit: u64,
@ -93,10 +104,12 @@ impl QueryInfo {
QueryInfo { QueryInfo {
table: table.to_string(), table: table.to_string(),
table_alias: None, table_alias: None,
joins_type: DatabaseQueryJoinType::NORMAL,
joins: Vec::new(), joins: Vec::new(),
conditions: collections::HashMap::new(), conditions: collections::HashMap::new(),
custom_where: None, custom_where: None,
custom_where_ars: vec![], custom_where_ars: vec![],
group_by: None,
limit: 0, limit: 0,
order: None, order: None,
fields: Vec::new(), fields: Vec::new(),
@ -109,6 +122,12 @@ impl QueryInfo {
self self
} }
/// Set join type
pub fn set_join_type(mut self, t: DatabaseQueryJoinType) -> QueryInfo {
self.joins_type = t;
self
}
pub fn join(mut self, table: &str, table_alias: &str, cond: &str) -> QueryInfo { pub fn join(mut self, table: &str, table_alias: &str, cond: &str) -> QueryInfo {
self.joins.push(QueryJoin { self.joins.push(QueryJoin {
table: table.to_string(), table: table.to_string(),
@ -194,9 +213,9 @@ impl QueryInfo {
self self
} }
/// Set the limit for the request /// Set GROUP BY clause
pub fn set_limit(mut self, value: u64) -> QueryInfo { pub fn set_group_by(mut self, group_by: &str) -> QueryInfo {
self.limit = value; self.group_by = Some(group_by.to_string());
self self
} }
@ -206,6 +225,12 @@ impl QueryInfo {
self self
} }
/// Set the limit for the request
pub fn set_limit(mut self, value: u64) -> QueryInfo {
self.limit = value;
self
}
/// Execute query /// Execute query
pub fn exec<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(self, process_function: F) pub fn exec<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(self, process_function: F)
-> Result<Vec<E>, Box<dyn Error>> { -> Result<Vec<E>, Box<dyn Error>> {
@ -396,6 +421,10 @@ pub fn query<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(info: QueryInfo, proce
} }
// Join conditions // Join conditions
if info.joins_type == DatabaseQueryJoinType::LEFT {
query.push_str(" LEFT ");
}
for j in info.joins { for j in info.joins {
query = query.add( query = query.add(
format!(" JOIN {} {} ON {} ", j.table, j.table_alias, j.condition).as_ref()); format!(" JOIN {} {} ON {} ", j.table, j.table_alias, j.condition).as_ref());
@ -427,6 +456,11 @@ pub fn query<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(info: QueryInfo, proce
params.append(&mut custom_args); params.append(&mut custom_args);
} }
// GROUP BY clause
if let Some(group_by) = info.group_by {
query.push_str(format!(" GROUP BY {} ", group_by).as_str())
}
// ORDER clause // ORDER clause
if let Some(order) = info.order { if let Some(order) = info.order {
query = query.add(format!(" ORDER BY {} ", order).as_str()); query = query.add(format!(" ORDER BY {} ", order).as_str());

View File

@ -11,4 +11,5 @@ pub mod groups_helper;
pub mod posts_helper; pub mod posts_helper;
pub mod conversations_helper; pub mod conversations_helper;
pub mod virtual_directory_helper; pub mod virtual_directory_helper;
pub mod movies_helper; pub mod movies_helper;
pub mod survey_helper;

View File

@ -5,7 +5,7 @@
use crate::constants::database_tables_names::POSTS_TABLE; use crate::constants::database_tables_names::POSTS_TABLE;
use crate::data::error::{ExecError, ResultBoxError}; use crate::data::error::{ExecError, ResultBoxError};
use crate::data::post::{Post, PostFile, PostKind, PostPageKind, PostVisibilityLevel, PostWebLink}; use crate::data::post::{Post, PostFile, PostKind, PostPageKind, PostVisibilityLevel, PostWebLink};
use crate::data::post::PostKind::{POST_KIND_COUNTDOWN, POST_KIND_IMAGE, POST_KIND_MOVIE, POST_KIND_PDF, POST_KIND_WEBLINK}; use crate::data::post::PostKind::{POST_KIND_COUNTDOWN, POST_KIND_IMAGE, POST_KIND_MOVIE, POST_KIND_PDF, POST_KIND_SURVEY, POST_KIND_WEBLINK};
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::helpers::{database, friends_helper}; use crate::helpers::{database, friends_helper};
use crate::utils::date_utils::time; use crate::utils::date_utils::time;
@ -174,6 +174,8 @@ fn db_to_post(res: &database::RowResult) -> ResultBoxError<Post> {
"count_down" => post.kind = POST_KIND_COUNTDOWN(res.get_u64("time_end").unwrap_or(0)), "count_down" => post.kind = POST_KIND_COUNTDOWN(res.get_u64("time_end").unwrap_or(0)),
"sondage" => post.kind = POST_KIND_SURVEY,
_ => {} _ => {}
} }

View File

@ -0,0 +1,63 @@
//! # Survey helper
//!
//! @author Pierre Hubert
use crate::constants::database_tables_names::{SURVEY_CHOICES_TABLE, SURVEY_INFO_TABLE, SURVEY_RESPONSE_TABLE};
use crate::data::error::ResultBoxError;
use crate::data::survey::{Survey, SurveyChoice};
use crate::data::user::UserID;
use crate::helpers::database;
/// Get information about a survey
pub fn get_info(post_id: u64) -> ResultBoxError<Survey> {
database::QueryInfo::new(SURVEY_INFO_TABLE)
.cond_u64("ID_texte", post_id)
.query_row(db_to_survey)
}
/// Get the choices of a survey
fn get_survey_choices(survey_id: u64) -> ResultBoxError<Vec<SurveyChoice>> {
database::QueryInfo::new(SURVEY_CHOICES_TABLE)
.alias("c")
.set_join_type(database::DatabaseQueryJoinType::LEFT)
.join(SURVEY_RESPONSE_TABLE, "r", "c.ID = r.ID_sondage_choix")
.cond_u64("c.ID_sondage", survey_id)
.set_group_by("c.ID")
.add_field("c.*")
.add_field("COUNT(r.ID) AS count_choice")
.exec(db_to_survey_choice)
}
/// Get the choice of a user for a survey
pub fn get_user_choice(survey_id: u64, user_id: &UserID) -> ResultBoxError<u64> {
Ok(database::QueryInfo::new(SURVEY_RESPONSE_TABLE)
.cond_u64("ID_sondage", survey_id)
.cond_user_id("ID_utilisateurs", user_id)
.exec(|r| r.get_u64("ID_sondage_choix"))?
.pop()
.unwrap_or(0))
}
/// Turn a database entry into a row object
fn db_to_survey(row: &database::RowResult) -> ResultBoxError<Survey> {
let survey_id = row.get_u64("ID")?;
Ok(Survey {
id: survey_id,
user_id: row.get_user_id("ID_utilisateurs")?,
time_create: row.get_date_as_time("date_creation")?,
post_id: row.get_u64("ID_texte")?,
question: row.get_str("question")?,
choices: get_survey_choices(survey_id)?,
allow_new_choices: row.get_legacy_bool("allow_new_choices")?,
})
}
/// Turn a database row into a survey choice
fn db_to_survey_choice(row: &database::RowResult) -> ResultBoxError<SurveyChoice> {
Ok(SurveyChoice {
id: row.get_u64("ID")?,
name: row.get_str("Choix")?,
count: row.get_u64("count_choice")?,
})
}