diff --git a/docs/db_struct.sql b/docs/db_struct.sql index 096a1cc..2f00ab0 100644 --- a/docs/db_struct.sql +++ b/docs/db_struct.sql @@ -307,10 +307,11 @@ CREATE TABLE `comunic_admin_log` ( PRIMARY KEY (`id`)); CREATE TABLE `comunic_reports` ( - `id` INT NOT NULL, + `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, - `target` VARCHAR(25) NOT NULL, + `target_type` VARCHAR(25) NOT NULL, `target_id` INT NOT NULL, `time` INT NOT NULL, + `cause` VARCHAR(20) NOT NULL, `comment` TEXT NULL, PRIMARY KEY (`id`)); diff --git a/docs/migration.sql b/docs/migration.sql index cd2b693..3b3b89f 100644 --- a/docs/migration.sql +++ b/docs/migration.sql @@ -1,9 +1,10 @@ -- Create report table CREATE TABLE `comunic_reports` ( - `id` INT NOT NULL, + `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, - `target` VARCHAR(25) NOT NULL, + `target_type` VARCHAR(25) NOT NULL, `target_id` INT NOT NULL, `time` INT NOT NULL, + `cause` VARCHAR(20) NOT NULL, `comment` TEXT NULL, PRIMARY KEY (`id`)); diff --git a/src/api_data/mod.rs b/src/api_data/mod.rs index 1764588..8f20b12 100644 --- a/src/api_data/mod.rs +++ b/src/api_data/mod.rs @@ -72,4 +72,5 @@ pub mod removed_user_from_conv_message; pub mod user_is_writing_message_in_conversation; pub mod res_create_conversation_for_group; pub mod notification_settings_api; -pub mod push_notifications_status_api; \ No newline at end of file +pub mod push_notifications_status_api; +pub mod submit_report_result_api; \ No newline at end of file diff --git a/src/api_data/submit_report_result_api.rs b/src/api_data/submit_report_result_api.rs new file mode 100644 index 0000000..0a260b7 --- /dev/null +++ b/src/api_data/submit_report_result_api.rs @@ -0,0 +1,12 @@ +use crate::data::report::ReportID; + +#[derive(serde::Serialize)] +pub struct SubmitReportResultApi { + report_id: u64, +} + +impl SubmitReportResultApi { + pub fn new(r: ReportID) -> Self { + Self { report_id: r.0 } + } +} \ No newline at end of file diff --git a/src/constants.rs b/src/constants.rs index c5dd0db..ed1d2d1 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -58,6 +58,9 @@ pub mod database_tables_names { /// Notifications table pub const NOTIFICATIONS_TABLE: &str = "comunic_notifications"; + /// Reports table + pub const REPORTS_TABLE: &str = "comunic_reports"; + /// Forez presence table pub const FOREZ_PRESENCE_TABLE: &str = "forez_presence"; diff --git a/src/controllers/report_controller.rs b/src/controllers/report_controller.rs index 0be2c07..7c37f8c 100644 --- a/src/controllers/report_controller.rs +++ b/src/controllers/report_controller.rs @@ -1,7 +1,15 @@ +use crate::api_data::http_error::HttpError; +use crate::api_data::submit_report_result_api::SubmitReportResultApi; +use crate::constants::reports; use crate::data::base_request_handler::BaseRequestHandler; use crate::data::config::conf; +use crate::data::group::GroupAccessLevel; use crate::data::http_request_handler::HttpRequestHandler; +use crate::data::post::PostAccessLevel; +use crate::data::report::{Report, REPORT_CAUSES, ReportID, ReportTarget}; +use crate::helpers::reports_helper; use crate::routes::RequestResult; +use crate::utils::date_utils::time; /// Submit a new report pub async fn report(r: &mut HttpRequestHandler) -> RequestResult { @@ -9,7 +17,66 @@ pub async fn report(r: &mut HttpRequestHandler) -> RequestResult { r.bad_request("Reporting is disabled!".to_string())?; } - // TODO : continue + let comment = r.post_string_without_html_opt("comment", 0)?; + if comment.as_ref().map(String::len).unwrap_or(0) as u32 > reports::MAX_COMMENT_LENGTH { + r.bad_request("Report comment is too long!".to_string())?; + } - r.success("go on") + let cause_id = r.post_string("cause")?; + let cause = r.some_or_bad_request( + REPORT_CAUSES.iter().find(|c| c.id().id().eq(&cause_id)), + "No valid report cause was given in the request!", + )?; + + let target = match r.post_string("target_type")?.as_str() { + "post" => + ReportTarget::Post( + r.post_post_with_access("target_id", PostAccessLevel::BASIC_ACCESS)?.id + ), + + "comment" => ReportTarget::Comment( + r.post_comment_with_access("target_id")?.id + ), + + "conversation" => ReportTarget::Conversation( + r.post_conv("target_id")?.conv_id + ), + + "conversation_message" => ReportTarget::ConversationMessage( + r.post_conv_message_id("target_id")?.id + ), + + "user" => ReportTarget::User( + r.post_user_id("target_id")? + ), + + "group" => ReportTarget::Group( + r.post_group_id_with_access("target_id", GroupAccessLevel::LIMITED_ACCESS)? + ), + + _ => { + r.bad_request("Unknown target type!".to_string())?; + unreachable!(); + } + }; + + let report = Report { + id: ReportID(0), + user_id: r.user_id()?, + target, + time: time(), + cause, + comment, + }; + + // Check for duplicate + if reports_helper::already_exists(&report)? { + r.set_error(HttpError::new(409, "You have already submitted a report for this resource!")); + return Ok(()); + } + + // Save report + let report_id = reports_helper::save_report(report)?; + + r.set_response(SubmitReportResultApi::new(report_id)) } \ No newline at end of file diff --git a/src/data/base_request_handler.rs b/src/data/base_request_handler.rs index de7a9ca..9b95c10 100644 --- a/src/data/base_request_handler.rs +++ b/src/data/base_request_handler.rs @@ -18,6 +18,7 @@ use crate::data::admin::AdminID; use crate::data::comment::Comment; use crate::data::config::conf; use crate::data::conversation::{ConversationMember, ConvID}; +use crate::data::conversation_message::ConversationMessage; use crate::data::custom_emoji::CustomEmoji; use crate::data::error::{ExecError, Res, ResultBoxError}; use crate::data::group::GroupAccessLevel; @@ -695,6 +696,23 @@ pub trait BaseRequestHandler { Ok(conv) } + /// Get & return information about a conversation message. The user must be a member of the + /// conversation + fn post_conv_message_id(&mut self, name: &str) -> Res { + let msg_id = self.post_u64(name)?; + + let conv_message = self.ok_or_not_found( + conversations_helper::get_single_message(msg_id), + "Requested conversation message was not found!", + )?; + + if conversations_helper::get_user_membership(&self.user_id()?, conv_message.conv_id).is_err() { + self.forbidden("You do not belong to the conversation attached to this message!".to_string())?; + } + + Ok(conv_message) + } + /// Get the ID fn post_group_id(&mut self, name: &str) -> ResultBoxError { let group_id = GroupID::new(self.post_u64(name)?); diff --git a/src/data/report.rs b/src/data/report.rs index 8239fd8..6ad0e9e 100644 --- a/src/data/report.rs +++ b/src/data/report.rs @@ -14,6 +14,19 @@ pub enum ReportTarget { Group(GroupID), } +impl ReportTarget { + pub fn to_db(&self) -> (&'static str, u64) { + match self { + ReportTarget::Post(i) => ("post", *i), + ReportTarget::Comment(i) => ("comment", *i), + ReportTarget::Conversation(i) => ("conversation", i.id()), + ReportTarget::ConversationMessage(i) => ("conv_msg", *i), + ReportTarget::User(i) => ("user", i.id()), + ReportTarget::Group(i) => ("group", i.id()), + } + } +} + #[derive(Eq, PartialEq, Copy, Clone)] pub struct ReportCauseId(&'static str); @@ -71,5 +84,6 @@ pub struct Report { pub user_id: UserID, pub target: ReportTarget, pub time: u64, + pub cause: &'static ReportCause, pub comment: Option, } \ No newline at end of file diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index f6fdf21..a985e8d 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -21,7 +21,9 @@ pub mod calls_helper; pub mod push_notifications_helper; pub mod independent_push_notifications_service_helper; pub mod firebase_notifications_helper; +pub mod reports_helper; pub mod forez_presence_helper; + pub mod admin_account_helper; pub mod admin_account_key_helper; pub mod admin_access_token_helper; diff --git a/src/helpers/reports_helper.rs b/src/helpers/reports_helper.rs new file mode 100644 index 0000000..a4fa340 --- /dev/null +++ b/src/helpers/reports_helper.rs @@ -0,0 +1,30 @@ +use crate::constants::database_tables_names::REPORTS_TABLE; +use crate::data::error::Res; +use crate::data::report::{Report, ReportID}; +use crate::helpers::database; + +/// Check if a report has already been saved by the same user on the same resource +pub fn already_exists(r: &Report) -> Res { + let (target_type, target_id) = r.target.to_db(); + + database::QueryInfo::new(REPORTS_TABLE) + .cond_user_id("user_id", &r.user_id) + .cond("target_type", target_type) + .cond_u64("target_id", target_id) + .exec_count_has_at_least_one_result() +} + +/// Save a new report in the database +pub fn save_report(r: Report) -> Res { + let (target_type, target_id) = r.target.to_db(); + + database::InsertQuery::new(REPORTS_TABLE) + .add_user_id("user_id", &r.user_id) + .add_str("target_type", target_type) + .add_u64("target_id", target_id) + .add_u64("time", r.time) + .add_str("cause", r.cause.id().id()) + .add_opt_str("comment", r.comment.as_ref()) + .insert_expect_result() + .map(|i| ReportID(i)) +} \ No newline at end of file