//! # Posts helper //! //! @author Pierre Hubert use crate::constants::database_tables_names::POSTS_TABLE; use crate::data::error::{ExecError, ResultBoxError}; use crate::data::group_id::GroupID; use crate::data::group_member::GroupMembershipLevel; use crate::data::movie::Movie; use crate::data::post::{Post, PostAccessLevel, PostFile, PostKind, PostPageKind, PostVisibilityLevel, PostWebLink}; use crate::data::post::PostKind::{POST_KIND_COUNTDOWN, POST_KIND_IMAGE, POST_KIND_MOVIE, POST_KIND_PDF, POST_KIND_SURVEY, POST_KIND_WEBLINK, POST_KIND_YOUTUBE}; use crate::data::user::UserID; use crate::helpers::{comments_helper, database, friends_helper, groups_helper, likes_helper, survey_helper, user_helper}; use crate::helpers::likes_helper::LikeType; use crate::utils::date_utils::{mysql_date, time}; use crate::utils::user_data_utils::user_data_path; impl PostVisibilityLevel { pub fn to_db(&self) -> u32 { match self { PostVisibilityLevel::VISIBILITY_PUBLIC => 1, PostVisibilityLevel::VISIBILITY_FRIENDS => 2, PostVisibilityLevel::VISIBILITY_USER => 3, PostVisibilityLevel::VISIBILITY_GROUP_MEMBERS => 50, } } pub fn from_db(val: u32) -> PostVisibilityLevel { match val { 1 => PostVisibilityLevel::VISIBILITY_PUBLIC, 2 => PostVisibilityLevel::VISIBILITY_FRIENDS, 3 => PostVisibilityLevel::VISIBILITY_USER, 50 => PostVisibilityLevel::VISIBILITY_GROUP_MEMBERS, _ => PostVisibilityLevel::VISIBILITY_PUBLIC, } } } impl PostKind { pub fn to_db(&self) -> String { match self { PostKind::POST_KIND_TEXT => "texte", POST_KIND_IMAGE(_) => "image", POST_KIND_WEBLINK(_) => "webpage_link", POST_KIND_PDF(_) => "pdf", POST_KIND_MOVIE(_) => "video", POST_KIND_COUNTDOWN(_) => "count_down", POST_KIND_SURVEY => "sondage", POST_KIND_YOUTUBE(_) => "youtube", }.to_string() } } /// Create a new post pub fn create(p: &Post) -> ResultBoxError { // Determine post target let (user_id, friend_id, group_id) = match &p.target_page { PostPageKind::PAGE_KIND_USER(user_id) => { (user_id, Some(&p.user_id), None) } PostPageKind::PAGE_KIND_GROUP(group_id) => { (&p.user_id, None, Some(group_id)) } }; // Start insert query let mut insert_query = database::InsertQuery::new(POSTS_TABLE) .add_user_id("ID_personne", user_id) .add_u64("ID_amis", friend_id.map(|f| f.id()).unwrap_or(0)) .add_u64("group_id", group_id.map(|f| f.id()).unwrap_or(0)) .add_str("date_envoi", &mysql_date()) .add_u64("time_insert", p.time_create) .add_u32("niveau_visibilite", p.visibility.to_db()) .add_str("type", &p.kind.to_db()) .add_opt_str("texte", p.content.as_ref()); match &p.kind { PostKind::POST_KIND_TEXT => { /* nothing to do */ } // Posts with associated file POST_KIND_IMAGE(file) | POST_KIND_PDF(file) => { insert_query = insert_query.add_str("path", &file.path) .add_usize("size", file.size) .add_opt_str("file_type", file.file_type.as_ref()); } // YouTube posts POST_KIND_YOUTUBE(id) => { insert_query = insert_query.add_str("path", id) .add_str("type", "youtube"); } // Movie post POST_KIND_MOVIE(id) => { insert_query = insert_query.add_u64("idvideo", *id); } // Weblink POST_KIND_WEBLINK(weblink) => { insert_query = insert_query .add_str("url_page", &weblink.url) .add_opt_str("titre_page", weblink.title.as_ref()) .add_opt_str("description_page", weblink.description.as_ref()) .add_opt_str("image_page", weblink.image.as_ref()); } // Countdown timer POST_KIND_COUNTDOWN(count_down) => { insert_query = insert_query.add_u64("time_end", *count_down); } // Survey POST_KIND_SURVEY => { /* Nothing to be done */ } } // Execute insertion let post_id = insert_query.insert()? .ok_or(ExecError::new("Insert post query did not return a result!"))?; Ok(post_id) } pub struct PostsQuery { /// The ID of the user making the request user_id: Option, /// Maximum number of posts to get limit: u64, /// Starting post start_from: u64, } impl PostsQuery { /// Construct a new request pub fn new(user_id: Option) -> PostsQuery { PostsQuery { user_id, limit: 10, start_from: 0, } } /// Start starting point pub fn set_start_from(mut self, start_from: u64) -> PostsQuery { self.start_from = start_from; self } /// Set the limit for this query pub fn set_limit(mut self, limit: u64) -> PostsQuery { self.limit = limit; self } /// Get the posts of a user pub fn get_user(self, user_id: &UserID) -> ResultBoxError> { get_user(&self, user_id) } /// Get the posts of a group pub fn get_group(self, group_id: &GroupID) -> ResultBoxError> { get_group(&self, group_id) } /// Get the latest posts for a user pub fn get_latest(self, include_groups_posts: bool) -> ResultBoxError> { get_latest(&self, include_groups_posts) } } /// Get the posts of `target_id` fn get_user(query: &PostsQuery, target_id: &UserID) -> ResultBoxError> { // Max user visibility let mut level = PostVisibilityLevel::VISIBILITY_PUBLIC; if let Some(user_id) = &query.user_id { if user_id == target_id { level = PostVisibilityLevel::VISIBILITY_USER; } else if friends_helper::are_friend(user_id, target_id)? { level = PostVisibilityLevel::VISIBILITY_FRIENDS; } } // Start request let mut db_query = database::QueryInfo::new(POSTS_TABLE); let mut custom_where = String::new(); // Preprocess conditions // ============= PERMISSION CONDITIONS ================= custom_where.push_str("((niveau_visibilite <= ?) "); db_query = db_query.add_custom_where_argument_u32(level.to_db()); // Add user posts (if signed in) if let Some(user_id) = &query.user_id { custom_where.push_str(" OR (ID_amis = ?) "); db_query = db_query.add_custom_where_argument_user_id(user_id); } custom_where.push_str(")"); // ============= /PERMISSION CONDITIONS ================= // ============== START POINT CONDITION ================= if query.start_from != 0 { custom_where.push_str(" AND ID <= ?"); db_query = db_query.add_custom_where_argument_u64(query.start_from); } // ============== /START POINT CONDITION ================ // Perform the request db_query .cond_user_id("ID_personne", target_id) .cond_u64("group_id", 0) .set_custom_where(&custom_where) .set_order("ID DESC") .set_limit(query.limit) .exec(db_to_post) } /// Get the list of posts of a group fn get_group(query: &PostsQuery, group_id: &GroupID) -> ResultBoxError> { let membership = groups_helper::get_membership_level(group_id, query.user_id.clone())?; let can_see_all_posts = membership <= GroupMembershipLevel::MEMBER; let visibility_level = match can_see_all_posts { true => PostVisibilityLevel::VISIBILITY_GROUP_MEMBERS, false => PostVisibilityLevel::VISIBILITY_FRIENDS }; // Prepare request let mut db_query = database::QueryInfo::new(POSTS_TABLE); let mut custom_where = String::new(); // =============== VISIBILITY CONDITION ================ custom_where.push_str("(niveau_visibilite <= ?)"); db_query = db_query.add_custom_where_argument_u32(visibility_level.to_db()); // ============== /VISIBILITY CONDITION ================ // ================== START POINT ====================== if query.start_from > 0 { custom_where.push_str(" AND ID <= ?"); db_query = db_query.add_custom_where_argument_u64(query.start_from); } // ================== /START POINT ===================== db_query .cond_group_id("group_id", group_id) .set_custom_where(&custom_where) .set_order("ID DESC") .set_limit(query.limit) .exec(db_to_post) } /// Get the latest posts of a user pub fn get_latest(query: &PostsQuery, include_group_posts: bool) -> ResultBoxError> { let user_id = query.user_id.as_ref().ok_or(ExecError::new("Can not get latest posts of no user!"))?; let visibility_level = PostVisibilityLevel::VISIBILITY_FRIENDS; // Get the list of friends of the user let friends_list = friends_helper::GetFriendsQuery::new(user_id) .set_only_accepted(true) .set_only_following(true) .exec()?; // Prepare the request let mut db_query = database::QueryInfo::new(POSTS_TABLE); let mut custom_where = String::new(); // ================== MEMBERSHIP CONDITION ====================== custom_where.push_str("("); // ========== FRIENDS POSTS =========== custom_where.push_str("(group_id = 0 AND niveau_visibilite <= ? AND (ID_personne = ?"); db_query = db_query.add_custom_where_argument_u32(visibility_level.to_db()); db_query = db_query.add_custom_where_argument_user_id(&user_id); for f in &friends_list { custom_where.push_str(" OR ID_personne = ?"); db_query = db_query.add_custom_where_argument_user_id(&f.friend_id); }; custom_where.push_str("))"); // ========== /FRIENDS POSTS ========== // =========== GROUPS POSTS =========== if include_group_posts { let groups = groups_helper::get_list_user(user_id, true)?; for g in &groups { custom_where.push_str(" OR group_id = ?"); db_query = db_query.add_custom_where_argument_group_id(g); }; } // ========== /GROUPS POSTS =========== custom_where.push_str(")"); // ================== /MEMBERSHIP CONDITION ===================== // ======================= START POINT ========================== if query.start_from > 0 { custom_where.push_str("AND ID <= ?"); db_query = db_query.add_custom_where_argument_u64(query.start_from); } // ====================== /START POINT ========================== db_query .set_custom_where(&custom_where) .set_order("ID DESC") .set_limit(query.limit) .exec(db_to_post) } /// Get information about a single post pub fn get_single(post_id: u64) -> ResultBoxError { database::QueryInfo::new(POSTS_TABLE) .cond_u64("ID", post_id) .query_row(db_to_post) } /// Get the entire list of posts of a given user + the posts created on user's page pub fn export_all_posts_user(user_id: &UserID) -> ResultBoxError> { database::QueryInfo::new(POSTS_TABLE) .set_custom_where("ID_personne = ? OR ID_amis = ?") .add_custom_where_argument_user_id(user_id) .add_custom_where_argument_user_id(user_id) .exec(db_to_post) } /// Get the entire list of posts of a given group pub fn export_all_posts_group(group_id: &GroupID) -> ResultBoxError> { database::QueryInfo::new(POSTS_TABLE) .cond_group_id("group_id", group_id) .exec(db_to_post) } /// Get the access level of a user over a post pub fn get_access_level(p: &Post, user_id: &Option) -> ResultBoxError { if user_id == &p.user_id.as_option() { return Ok(PostAccessLevel::FULL_ACCESS); } match &p.target_page { // User page PostPageKind::PAGE_KIND_USER(user_page_id) => { if &user_page_id.as_option() == user_id { return Ok(PostAccessLevel::INTERMEDIATE_ACCESS); } return match p.visibility { PostVisibilityLevel::VISIBILITY_PUBLIC => { if user_helper::can_see_user_page(user_id.as_ref().unwrap_or(&UserID::invalid()), user_page_id)? { Ok(PostAccessLevel::BASIC_ACCESS) } else { Ok(PostAccessLevel::NO_ACCESS) } } PostVisibilityLevel::VISIBILITY_FRIENDS => { if user_id.is_some() && friends_helper::are_friend(user_id.as_ref().unwrap_or(&UserID::invalid()), user_page_id)? { Ok(PostAccessLevel::BASIC_ACCESS) } else { Ok(PostAccessLevel::NO_ACCESS) } } // No access to posts with restricted visibility PostVisibilityLevel::VISIBILITY_USER => Ok(PostAccessLevel::NO_ACCESS), _ => Ok(PostAccessLevel::NO_ACCESS), }; } // Group page PostPageKind::PAGE_KIND_GROUP(group_id) => { let access_level = groups_helper::get_membership_level(group_id, user_id.clone())?; // Moderators & administrators if access_level < GroupMembershipLevel::MEMBER { Ok(PostAccessLevel::INTERMEDIATE_ACCESS) } else if access_level == GroupMembershipLevel::MEMBER { Ok(PostAccessLevel::BASIC_ACCESS) } else if p.visibility != PostVisibilityLevel::VISIBILITY_PUBLIC || !groups_helper::is_open(group_id)? { Ok(PostAccessLevel::NO_ACCESS) } else { Ok(PostAccessLevel::BASIC_ACCESS) } } } } /// Check out whether it is possible to create comments on a post or not pub fn allow_comments_on_post(p: &Post) -> ResultBoxError { Ok( !p.is_on_user_page() || user_helper::allow_comments(p.user_page_id().unwrap_or(&UserID::invalid()))?) } /// Set a new visibility level to a post pub fn set_level(post_id: u64, level: PostVisibilityLevel) -> ResultBoxError { database::UpdateInfo::new(POSTS_TABLE) .cond_u64("ID", post_id) .set_u32("niveau_visibilite", level.to_db()) .exec() } /// Set a new content to the post pub fn set_content(post_id: u64, new_content: &str) -> ResultBoxError { database::UpdateInfo::new(POSTS_TABLE) .cond_u64("ID", post_id) .set_str("texte", new_content) .exec() } /// Delete a post pub fn delete(p: &Post) -> ResultBoxError { // TODO : delete all the notifications related with the post // Delete all the likes associated with the post likes_helper::delete_all(p.id, LikeType::POST)?; // Delete all the comments associated to the post comments_helper::delete_all(p.id)?; // Delete associated file / resource (if any) match &p.kind { // Image / PDF PostKind::POST_KIND_IMAGE(file) | PostKind::POST_KIND_PDF(file) => { let path = user_data_path(file.path.as_ref()); if path.exists() { std::fs::remove_file(path)?; } } // Survey PostKind::POST_KIND_SURVEY => { if survey_helper::exists(p.id)? { survey_helper::delete(p.id)?; } } _ => { /* Nothing to be done */ } } // Remove the post from the database database::DeleteQuery::new(POSTS_TABLE) .cond_u64("ID", p.id) .exec()?; Ok(()) } /// Delete all the posts related with a group pub fn delete_all_group(group_id: &GroupID) -> ResultBoxError { for post in export_all_posts_group(group_id)? { delete(&post)?; } Ok(()) } /// Get all the posts that use of movie pub fn get_posts_for_movie(m: &Movie) -> ResultBoxError> { database::QueryInfo::new(POSTS_TABLE) .cond_u64("idvideo", m.id) .exec(db_to_post) } /// Delete all the posts associated with a movie pub fn delete_all_with_movie(m: &Movie) -> ResultBoxError { for post in get_posts_for_movie(m)? { delete(&post)?; } Ok(()) } /// Turn a post into a database entry fn db_to_post(res: &database::RowResult) -> ResultBoxError { let user_id = if res.get_u64("ID_amis")? == 0 { res.get_user_id("ID_personne") } else { res.get_user_id("ID_amis") }?; let target_page = if res.get_u64("group_id")? == 0 { PostPageKind::PAGE_KIND_USER(res.get_user_id("ID_personne")?) } else { PostPageKind::PAGE_KIND_GROUP(res.get_group_id("group_id")?) }; let file = match res.get_optional_str("path")? { None => None, Some(path) => Some(PostFile { path, size: res.get_usize("size").unwrap_or(0), file_type: res.get_optional_str("file_type")?, }), }; let mut post = Post { // General information id: res.get_u64("ID")?, user_id: user_id.clone(), time_create: res.get_u64("time_insert").unwrap_or(time()), target_page, content: res.get_optional_str("texte")?, visibility: PostVisibilityLevel::from_db(res.get_u32("niveau_visibilite")?), kind: PostKind::POST_KIND_TEXT, }; let file = file.ok_or(ExecError::new("A file is required with this post type!")); match res.get_str("type")?.as_str() { "image" => post.kind = POST_KIND_IMAGE(file?), "webpage_link" => post.kind = POST_KIND_WEBLINK(PostWebLink { url: res.get_str("url_page")?, title: res.get_optional_str("titre_page")?, description: res.get_optional_str("description_page")?, image: res.get_optional_str("image_page")?, }), "pdf" => post.kind = POST_KIND_PDF(file?), "video" => post.kind = POST_KIND_MOVIE(res.get_u64("idvideo")?), "count_down" => post.kind = POST_KIND_COUNTDOWN(res.get_u64("time_end").unwrap_or(0)), "sondage" => post.kind = POST_KIND_SURVEY, "youtube" => post.kind = POST_KIND_YOUTUBE(res.get_str("path")?), _ => {} } Ok(post) }