diff --git a/docs/db_struct.sql b/docs/db_struct.sql index 4443660..f70d213 100644 --- a/docs/db_struct.sql +++ b/docs/db_struct.sql @@ -261,4 +261,13 @@ CREATE TABLE `comunic_custom_emojis` ( `user_id` INT NULL, `shortcut` VARCHAR(45) NULL, `path` VARCHAR(255) NULL, - PRIMARY KEY (`id`)); \ No newline at end of file + PRIMARY KEY (`id`)); + +CREATE TABLE `forez_presence` ( + `id` INT NOT NULL AUTO_INCREMENT, + `user_id` INT NULL, + `group_id` INT NULL, + `year` INT NULL, + `month` INT NULL, + `day` INT NULL, + PRIMARY KEY (`id`)); diff --git a/docs/migration.sql b/docs/migration.sql index 84a3b19..14caa49 100644 --- a/docs/migration.sql +++ b/docs/migration.sql @@ -1,4 +1,12 @@ --- Nothing yet ALTER TABLE `utilisateurs` ADD COLUMN `is_email_public` INT NULL DEFAULT 0 AFTER `allow_notif_sound`, ADD COLUMN `location` VARCHAR(45) NULL AFTER `is_email_visible`; + +CREATE TABLE `comunic`.`forez_presence` ( + `id` INT NOT NULL AUTO_INCREMENT, + `user_id` INT NULL, + `group_id` INT NULL, + `year` INT NULL, + `month` INT NULL, + `day` INT NULL, + PRIMARY KEY (`id`)); diff --git a/src/constants.rs b/src/constants.rs index 195eafc..64abf0b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -57,6 +57,9 @@ pub mod database_tables_names { /// Notifications table pub const NOTIFICATIONS_TABLE: &str = "comunic_notifications"; + + /// Forez presence table + pub const FOREZ_PRESENCE_TABLE: &str = "forez_presence"; } /// Push Notifications Database prefix diff --git a/src/controllers/forez_controller.rs b/src/controllers/forez_controller.rs index 3eb5e8b..ebeb7a0 100644 --- a/src/controllers/forez_controller.rs +++ b/src/controllers/forez_controller.rs @@ -5,12 +5,14 @@ //! //! @author Pierre Hubert -use crate::data::http_request_handler::HttpRequestHandler; -use crate::routes::RequestResult; -use crate::data::config::conf; -use crate::data::base_request_handler::BaseRequestHandler; use crate::api_data::group_api::GroupApi; -use crate::helpers::groups_helper; +use crate::data::base_request_handler::BaseRequestHandler; +use crate::data::config::conf; +use crate::data::http_request_handler::HttpRequestHandler; +use crate::data::presence::Presence; +use crate::data::user_ws_request_handler::UserWsRequestHandler; +use crate::helpers::{forez_presence_helper, groups_helper}; +use crate::routes::RequestResult; /// Get the list of declared Forez groups in the application pub fn get_list_groups(r: &mut HttpRequestHandler) -> RequestResult { @@ -21,4 +23,115 @@ pub fn get_list_groups(r: &mut HttpRequestHandler) -> RequestResult { } r.set_response(list) +} + +/// Set presence +/// +/// Presences format: YYYY,MM,DD;YYYY,MM,DD;... +pub fn set_presence(r: &mut UserWsRequestHandler) -> RequestResult { + let group = r.post_forez_group("group")?; + let presences = r.post_string_opt("presence", 0, false)?; + + let mut list = vec![]; + for p in presences.split(";") { + if p == "" { + continue; + } + + let info: Vec<&str> = p.split(",").collect(); + + if info.len() != 3 { + r.bad_request("Invalid presence information!".to_string())?; + } + + let year = info[0].parse::()?; + let month = info[1].parse::()?; + let day = info[2].parse::()?; + + if year < 2020 || year > 2100 { + r.bad_request("Invalid year specified!".to_string())?; + } + + if month < 1 || month > 12 { + r.bad_request("Invalid month specified!".to_string())?; + } + + if day < 1 || day > 31 { + r.bad_request("Invalid day specified!".to_string())?; + } + + let presence = Presence { + id: 0, + user_id: r.user_id()?, + year, + month, + day, + }; + + if !list.contains(&presence) { + list.push(presence); + } + } + + forez_presence_helper::update(&group, &r.user_id()?, list)?; + + r.success("Presences updated.") +} + +/// Get the list of presences of all the members of a group +/// +/// Format: USER_ID,YYYY,MM,DD +pub fn get_list(r: &mut H) -> RequestResult { + let group = r.post_forez_group("group")?; + let list = forez_presence_helper::get_list(&group)?; + + let list = list + .iter() + .map(|p| format!("{},{},{},{}", p.user_id.id(), p.year, p.month, p.day)) + .collect::>(); + + r.set_response(list) +} + +/// Add a new day of presence +pub fn add_day(r: &mut H) -> RequestResult { + let group = r.post_forez_group("group")?; + + let mut list = forez_presence_helper::get_user_presences(&group, &r.user_id()?)?; + + let presence = Presence { + id: 0, + user_id: r.user_id()?, + year: r.post_u32("year")?, + month: r.post_u16("month")?, + day: r.post_u16("day")?, + }; + + if !list.contains(&presence) { + list.push(presence); + } + + forez_presence_helper::update(&group, &r.user_id()?, list)?; + + r.success("Updated presences.") +} + +/// Remove a day of presence +pub fn del_day(r: &mut H) -> RequestResult { + let group = r.post_forez_group("group")?; + + let mut list = forez_presence_helper::get_user_presences(&group, &r.user_id()?)?; + + let presence = Presence { + id: 0, + user_id: r.user_id()?, + year: r.post_u32("year")?, + month: r.post_u16("month")?, + day: r.post_u16("day")?, + }; + + list.retain(|el| el != &presence); + forez_presence_helper::update(&group, &r.user_id()?, list)?; + + r.success("Updated presences.") } \ No newline at end of file diff --git a/src/data/base_request_handler.rs b/src/data/base_request_handler.rs index 2f118a2..be5cfd3 100644 --- a/src/data/base_request_handler.rs +++ b/src/data/base_request_handler.rs @@ -14,6 +14,7 @@ use serde::Serialize; use crate::api_data::http_error::HttpError; use crate::constants::PASSWORD_MIN_LENGTH; use crate::data::comment::Comment; +use crate::data::config::conf; use crate::data::conversation::{ConversationMember, ConvID}; use crate::data::custom_emoji::CustomEmoji; use crate::data::error::{ExecError, Res, ResultBoxError}; @@ -484,6 +485,14 @@ pub trait BaseRequestHandler { Ok(self.post_string(name)?.parse::()?) } + fn post_u32(&mut self, name: &str) -> Res { + Ok(self.post_u64(name)? as u32) + } + + fn post_u16(&mut self, name: &str) -> Res { + Ok(self.post_u64(name)? as u16) + } + fn post_positive_u64_opt(&mut self, name: &str) -> Res> { match self.post_u64_opt(name, 0)? { 0 => Ok(None), @@ -796,4 +805,15 @@ pub trait BaseRequestHandler { } } } + + /// Get a membership to a Forez group + fn post_forez_group(&mut self, name: &str) -> Res { + let group = self.post_group_id_with_access(name, GroupAccessLevel::MEMBER_ACCESS)?; + + if !conf().forez_groups.contains(&group) { + self.bad_request(format!("The group {} is not a Forez group!", group.id()))?; + } + + Ok(group) + } } \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index 219b9db..7af769c 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -39,4 +39,5 @@ pub mod user_ws_message; pub mod user_ws_connection; pub mod call_signal; pub mod new_notifications_settings; -pub mod push_notification; \ No newline at end of file +pub mod push_notification; +pub mod presence; \ No newline at end of file diff --git a/src/data/presence.rs b/src/data/presence.rs new file mode 100644 index 0000000..d72255f --- /dev/null +++ b/src/data/presence.rs @@ -0,0 +1,22 @@ +//! # Presence information +//! +//! @author Pierre Hubert + +use crate::data::user::UserID; + +pub struct Presence { + pub id: u64, + pub user_id: UserID, + pub year: u32, + pub month: u16, + pub day: u16, +} + +impl PartialEq for Presence { + fn eq(&self, other: &Self) -> bool { + self.user_id == other.user_id && + self.year == other.year && + self.month == other.month && + self.day == other.day + } +} \ No newline at end of file diff --git a/src/helpers/database.rs b/src/helpers/database.rs index d3ba883..ab5a28f 100644 --- a/src/helpers/database.rs +++ b/src/helpers/database.rs @@ -350,6 +350,16 @@ impl<'a> RowResult<'a> { } } + pub fn get_u16(&self, name: &str) -> ResultBoxError { + let value = self.row.get_opt(self.find_col(name)?); + + match value { + None => Err(ExecError::boxed_string( + format!("Could not extract integer field {} !", name))), + Some(s) => Ok(s?) + } + } + /// Find an integer included in the request pub fn get_usize(&self, name: &str) -> Result> { let value = self.row.get_opt(self.find_col(name)?); @@ -661,6 +671,11 @@ impl InsertQuery { self } + pub fn add_u16(mut self, key: &str, value: u16) -> InsertQuery { + self.values.insert(key.to_string(), Value::from(value)); + self + } + pub fn add_user_id(mut self, key: &str, value: &UserID) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value.id())); self diff --git a/src/helpers/forez_presence_helper.rs b/src/helpers/forez_presence_helper.rs new file mode 100644 index 0000000..a33d58d --- /dev/null +++ b/src/helpers/forez_presence_helper.rs @@ -0,0 +1,62 @@ +//! # Forez Presence helper +//! +//! @author Pierre Hubert + +use crate::constants::database_tables_names::FOREZ_PRESENCE_TABLE; +use crate::data::error::Res; +use crate::data::group_id::GroupID; +use crate::data::presence::Presence; +use crate::data::user::UserID; +use crate::helpers::database; + +/// Get the list of presence contained in the database +pub fn get_list(group_id: &GroupID) -> Res> { + database::QueryInfo::new(FOREZ_PRESENCE_TABLE) + .cond_group_id("group_id", group_id) + .exec(db_to_presence) +} + +/// Get the list of presences of a specific user +pub fn get_user_presences(group_id: &GroupID, user_id: &UserID) -> Res> { + database::QueryInfo::new(FOREZ_PRESENCE_TABLE) + .cond_user_id("user_id", user_id) + .cond_group_id("group_id", group_id) + .exec(db_to_presence) +} + +/// Update the presences of a user +pub fn update(group_id: &GroupID, user_id: &UserID, list: Vec) -> Res { + let previous_presences = get_user_presences(group_id, user_id)?; + for presence in &list { + if !previous_presences.contains(presence) { + database::InsertQuery::new(FOREZ_PRESENCE_TABLE) + .add_user_id("user_id", user_id) + .add_group_id("group_id", group_id) + .add_u32("year", presence.year) + .add_u16("month", presence.month) + .add_u16("day", presence.day) + .insert()?; + } + } + + for prev in &previous_presences { + if !list.contains(prev) { + database::DeleteQuery::new(FOREZ_PRESENCE_TABLE) + .cond_u64("id", prev.id) + .exec()?; + } + } + + Ok(()) +} + +/// Turn a database entry into a presence entry +fn db_to_presence(row: &database::RowResult) -> Res { + Ok(Presence { + id: row.get_u64("id")?, + user_id: row.get_user_id("user_id")?, + year: row.get_u32("year")?, + month: row.get_u16("month")?, + day: row.get_u16("day")?, + }) +} \ No newline at end of file diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 2357b5b..d7c0e25 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -20,4 +20,5 @@ pub mod events_helper; pub mod calls_helper; pub mod push_notifications_helper; pub mod independent_push_notifications_service_helper; -pub mod firebase_notifications_helper; \ No newline at end of file +pub mod firebase_notifications_helper; +pub mod forez_presence_helper; \ No newline at end of file diff --git a/src/user_ws_routes.rs b/src/user_ws_routes.rs index f1d2170..b49883c 100644 --- a/src/user_ws_routes.rs +++ b/src/user_ws_routes.rs @@ -2,7 +2,7 @@ //! //! @author Pierre Hubert -use crate::controllers::{calls_controller, conversations_controller, likes_controller, user_ws_actions}; +use crate::controllers::{calls_controller, conversations_controller, forez_controller, likes_controller, user_ws_actions}; use crate::data::error::Res; use crate::data::user_ws_request_handler::UserWsRequestHandler; @@ -49,6 +49,11 @@ pub fn get_user_ws_routes() -> Vec { UserWsRoute::new("calls/mark_ready", calls_controller::mark_user_ready), UserWsRoute::new("calls/request_offer", calls_controller::request_offer), UserWsRoute::new("calls/stop_streaming", calls_controller::stop_streaming), + + // Presence controller + UserWsRoute::new("forez_presence/list", forez_controller::get_list), + UserWsRoute::new("forez_presence/add_day", forez_controller::add_day), + UserWsRoute::new("forez_presence/del_day", forez_controller::del_day), ] }