diff --git a/src/api_data/conversation_api.rs b/src/api_data/conversation_api.rs new file mode 100644 index 0000000..c78a923 --- /dev/null +++ b/src/api_data/conversation_api.rs @@ -0,0 +1,57 @@ +//! # Conversation API object +//! +//! @author Pierre Hubert +use serde::{Serialize, Serializer}; +use crate::api_data::legacy_api_bool::LegacyBool; +use crate::data::conversation::Conversation; + +/// Special implementation of conversation name (false if none / the name otherwise) +struct ConvName(Option); + +impl Serialize for ConvName { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + match &self.0 { + None => serializer.serialize_bool(false), + Some(n) => serializer.serialize_str(n) + } + } +} + +#[derive(Serialize)] +#[allow(non_snake_case)] +pub struct ConversationAPI { + ID: u64, + ID_owner: u64, + last_active: u64, + name: ConvName, + following: LegacyBool, + saw_last_message: LegacyBool, + members: Vec, + canEveryoneAddMembers: bool, + can_have_call: bool, + can_have_video_call: bool, + has_call_now: bool, +} + +impl ConversationAPI { + + /// Construct a new Conversation instance + pub fn new(conv: &Conversation) -> ConversationAPI { + ConversationAPI { + ID: conv.id, + ID_owner: conv.owner_id as u64, + last_active: conv.last_active, + name: ConvName(conv.name.clone()), + following: LegacyBool(conv.following), + saw_last_message: LegacyBool(conv.saw_last_message), + members: conv.members.iter().map(|x| x.clone() as u64).collect(), + canEveryoneAddMembers: conv.can_everyone_add_members, + + // TODO : update when call system is implemented + can_have_call: false, + can_have_video_call: false, + has_call_now: false + } + } +} \ No newline at end of file diff --git a/src/api_data/legacy_api_bool.rs b/src/api_data/legacy_api_bool.rs new file mode 100644 index 0000000..57547ca --- /dev/null +++ b/src/api_data/legacy_api_bool.rs @@ -0,0 +1,20 @@ +//! # Legacy API boolean +//! +//! true => 1 +//! false => 0 + + +use serde::{Serialize, Serializer}; + +/// Special implementation of conversation name (false if none / the name otherwise) +pub struct LegacyBool(pub bool); + +impl Serialize for LegacyBool { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + match &self.0 { + true => serializer.serialize_i8(1), + false => serializer.serialize_i8(0), + } + } +} \ No newline at end of file diff --git a/src/api_data/mod.rs b/src/api_data/mod.rs index fd1e956..b0999c2 100644 --- a/src/api_data/mod.rs +++ b/src/api_data/mod.rs @@ -14,4 +14,6 @@ pub mod user_info; pub mod custom_emoji; pub mod res_find_user_by_virtual_directory; pub mod res_find_virtual_directory; -pub mod res_create_conversation; \ No newline at end of file +pub mod res_create_conversation; +pub mod conversation_api; +mod legacy_api_bool; \ No newline at end of file diff --git a/src/controllers/conversations_controller.rs b/src/controllers/conversations_controller.rs index 7804e92..a495155 100644 --- a/src/controllers/conversations_controller.rs +++ b/src/controllers/conversations_controller.rs @@ -7,6 +7,7 @@ use crate::controllers::routes::RequestResult; use crate::helpers::{user_helper, conversations_helper}; use crate::data::new_conversation::NewConversation; use crate::api_data::res_create_conversation::ResCreateConversation; +use crate::api_data::conversation_api::ConversationAPI; /// Create a new conversation pub fn create(r: &mut HttpRequestHandler) -> RequestResult { @@ -47,5 +48,8 @@ pub fn create(r: &mut HttpRequestHandler) -> RequestResult { /// Get the list of conversations of a user pub fn get_list(r: &mut HttpRequestHandler) -> RequestResult { - r.success("Get the list of conversations") + + let list = conversations_helper::get_list_user(r.user_id()?)?; + + r.set_response(list.iter().map(|c| ConversationAPI::new(c)).collect::>()) } \ No newline at end of file diff --git a/src/data/conversation.rs b/src/data/conversation.rs new file mode 100644 index 0000000..48772c2 --- /dev/null +++ b/src/data/conversation.rs @@ -0,0 +1,19 @@ +//! # Conversation information +//! +//! @author Pierre Hubert + +use crate::data::user::UserID; + +#[derive(Debug)] +pub struct Conversation { + pub id: u64, + pub owner_id: UserID, + pub name: Option, + pub members: Vec, + pub can_everyone_add_members: bool, + pub last_active: u64, + pub time_create: u64, + + pub following: bool, + pub saw_last_message: bool, +} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index 1e33f43..06fbdc6 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -7,4 +7,5 @@ pub mod api_client; pub mod user; pub mod user_token; pub mod custom_emoji; -pub mod new_conversation; \ No newline at end of file +pub mod new_conversation; +pub mod conversation; \ No newline at end of file diff --git a/src/helpers/conversations_helper.rs b/src/helpers/conversations_helper.rs index 96c3929..8567604 100644 --- a/src/helpers/conversations_helper.rs +++ b/src/helpers/conversations_helper.rs @@ -4,10 +4,12 @@ use crate::data::new_conversation::NewConversation; use crate::data::error::{ResultBoxError, ExecError}; -use crate::helpers::database::InsertQuery; +use crate::helpers::database::{InsertQuery}; use crate::constants::database_tables_names::{CONV_LIST_TABLE, CONV_USERS_TABLE}; use crate::utils::date_utils::time; use crate::data::user::UserID; +use crate::data::conversation::Conversation; +use crate::helpers::database; /// Create a new conversation. This method returns the ID of the created conversation pub fn create(conv: &NewConversation) -> ResultBoxError { @@ -45,4 +47,51 @@ pub fn add_member(conv_id: u64, user_id: UserID, following: bool) -> ResultBoxEr .insert()?; Ok(()) +} + +/// Get the list of conversations of a specific user +pub fn get_list_user(user_id: UserID) -> ResultBoxError> { + database::QueryInfo::new(CONV_LIST_TABLE) + .alias("l") + + // Join with conversation members table + .join(CONV_USERS_TABLE, "u", "l.id = u.conv_id") + + // Specify selected fields + .add_field("*") + .add_field("l.id as id") + .add_field("l.user_id as owner_id") + + // Filter query + .cond_user_id("u.user_id", user_id) + + // Sort results + .set_order("l.last_active DESC") + + // Execute query + .exec(db_to_conversation_info) +} + +/// Get the list of members of a conversation +pub fn get_list_members(conv_id: u64) -> ResultBoxError> { + database::QueryInfo::new(CONV_USERS_TABLE) + .cond_u64("conv_id", conv_id) + .add_field("user_id") + .exec(|res|res.get_user_id("user_id")) +} + +/// Turn a database entry into a ConversationInfo object +fn db_to_conversation_info(row: &database::RowResult) -> ResultBoxError { + let conv_id = row.get_u64("id")?; + Ok(Conversation { + id: conv_id, + owner_id: row.get_user_id("owner_id")?, + name: row.get_optional_str("name")?, + members: get_list_members(conv_id)?, + can_everyone_add_members: row.get_legacy_bool("can_everyone_add_members")?, + last_active: row.get_u64("last_active")?, + time_create: row.get_u64("time_add")?, + following: row.get_legacy_bool("following")?, + saw_last_message: row.get_legacy_bool("saw_last_message")?, + }) } \ No newline at end of file diff --git a/src/helpers/database.rs b/src/helpers/database.rs index 50c99b8..fefd8da 100644 --- a/src/helpers/database.rs +++ b/src/helpers/database.rs @@ -50,10 +50,20 @@ pub fn get_connection() -> Result> { Ok(pool.get_conn()?) } +/// Structure used to implement JOIN on queries +struct QueryJoin { + table: String, + table_alias: String, + condition: String, +} pub struct QueryInfo { /// Fetched table pub table: String, + pub table_alias: Option, + + /// Joins + joins: Vec, /// Query limits pub conditions: collections::HashMap, @@ -61,6 +71,9 @@ pub struct QueryInfo { /// Limit of the query (0 = no limit) pub limit: u64, + /// Order of the query + pub order: Option, + /// Queried arguments /// /// If this attribute is empty, all the columns of the table set are fetched @@ -72,12 +85,30 @@ impl QueryInfo { pub fn new(table: &str) -> QueryInfo { QueryInfo { table: table.to_string(), + table_alias: None, + joins: Vec::new(), conditions: collections::HashMap::new(), limit: 0, + order: None, fields: Vec::new(), } } + /// Main fetched table alias + pub fn alias(mut self, table_alias: &str) -> QueryInfo { + self.table_alias = Some(table_alias.to_string()); + self + } + + pub fn join(mut self, table: &str, table_alias: &str, cond: &str) -> QueryInfo { + self.joins.push(QueryJoin { + table: table.to_string(), + table_alias: table_alias.to_string(), + condition: cond.to_string(), + }); + self + } + pub fn cond(mut self, key: &str, val: &str) -> QueryInfo { self.conditions.insert(key.to_string(), val.to_string()); self @@ -109,6 +140,12 @@ impl QueryInfo { self } + /// Set results ordering + pub fn set_order(mut self, order: &str) -> QueryInfo { + self.order = Some(order.to_string()); + self + } + /// Execute query pub fn exec ProcessRowResult>(self, process_function: F) -> Result, Box> { @@ -187,6 +224,11 @@ impl<'a> RowResult<'a> { } } + /// Get the ID of a user included in the request + pub fn get_user_id(&self, name: &str) -> ResultBoxError { + self.get_int64(name) + } + /// Find a string included in the request pub fn get_str(&self, name: &str) -> Result> { let value = self.row.get_opt(self.find_col(name)?); @@ -273,6 +315,17 @@ pub fn query ProcessRowResult>(info: QueryInfo, proce // Build query let mut query = format!("SELECT {} FROM {} ", select_elements, info.table); + // Table alias (if any) + if let Some(alias) = info.table_alias { + query = query.add(format!(" {} ", alias).as_str()); + } + + // Join conditions + for j in info.joins { + query = query.add( + format!(" JOIN {} {} ON {} ", j.table, j.table_alias, j.condition).as_ref()); + } + // WHERE clause if !info.conditions.is_empty() { @@ -287,6 +340,11 @@ pub fn query ProcessRowResult>(info: QueryInfo, proce query = query.add(&where_args); } + // ORDER clause + if let Some(order) = info.order { + query = query.add(format!(" ORDER BY {} ", order).as_str()); + } + // LIMIT clause if info.limit > 0 { query = query.add(&format!(" LIMIT {}", info.limit)); @@ -294,7 +352,10 @@ pub fn query ProcessRowResult>(info: QueryInfo, proce // Execute query let mut con = get_connection()?; - let stmt = con.prep(&query)?; + let stmt = con.prep(&query).or_else(|err| { + println!("Error in SQL query: {}", &query); + Err(err) + })?; let mut res = con.exec_iter(stmt, params)?;