diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs index 8cec6ce3c2c33d08e5766bfdbfd5cde26099b86b..13975de4f7c0f678134a482aca9740071fd15bc9 100644 --- a/src/database/entities/message.rs +++ b/src/database/entities/message.rs @@ -58,4 +58,24 @@ impl Message { Ok(()) } + + pub async fn publish_edit(self) -> Result<()> { + let channel = self.channel.clone(); + ClientboundNotification::MessageEdit(self) + .publish(channel) + .await + .ok(); + + Ok(()) + } + + pub async fn publish_delete(self) -> Result<()> { + let channel = self.channel.clone(); + ClientboundNotification::MessageDelete(self.id) + .publish(channel) + .await + .ok(); + + Ok(()) + } } diff --git a/src/database/guards/reference.rs b/src/database/guards/reference.rs index a1262c33e1517267ee1fe27f3097c2741a2b241d..829ff244e2bd8d4ec7fc4b4221066cf403ad7b47 100644 --- a/src/database/guards/reference.rs +++ b/src/database/guards/reference.rs @@ -1,7 +1,7 @@ use crate::database::*; use crate::util::result::{Error, Result}; -use mongodb::bson::{doc, from_bson, from_document, Bson}; +use mongodb::bson::{doc, from_document}; use rocket::http::RawStr; use rocket::request::FromParam; use serde::{de::DeserializeOwned, Deserialize, Serialize}; diff --git a/src/database/guards/user.rs b/src/database/guards/user.rs index c7363d1a3ce6e4b80a34ecea7884cd6c195d29ec..fb8e53dc24f4838c1668169eaaec0ddad1b60b65 100644 --- a/src/database/guards/user.rs +++ b/src/database/guards/user.rs @@ -1,6 +1,6 @@ use crate::database::*; -use mongodb::bson::{doc, from_bson, from_document, Bson}; +use mongodb::bson::{doc, from_document}; use rauth::auth::Session; use rocket::http::Status; use rocket::request::{self, FromRequest, Outcome, Request}; diff --git a/src/database/migrations/scripts.rs b/src/database/migrations/scripts.rs index 64866c036fba29e3de9e5fb4f1866b12cd2d32c6..4f570007328613486597208d77be8643692e152d 100644 --- a/src/database/migrations/scripts.rs +++ b/src/database/migrations/scripts.rs @@ -2,7 +2,7 @@ use super::super::{get_collection, get_db}; use crate::rocket::futures::StreamExt; use log::info; -use mongodb::bson::{doc, from_bson, from_document, Bson}; +use mongodb::bson::{doc, from_document}; use mongodb::options::FindOptions; use serde::{Deserialize, Serialize}; diff --git a/src/notifications/events.rs b/src/notifications/events.rs index eeb03a9fe673774761dc097f8f9c6dde608c9cd4..cde3946232aaed49d024a827d0c4904b0d1b82f5 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -37,6 +37,8 @@ pub enum ClientboundNotification { }, Message(Message), + MessageEdit(Message), + MessageDelete(String), /*MessageCreate { id: String, diff --git a/src/notifications/payload.rs b/src/notifications/payload.rs index c3b25599e941bbe51d201e38b17d5038a839e257..32ae01034064f7bd08519f8f715dadb3ef05d4a5 100644 --- a/src/notifications/payload.rs +++ b/src/notifications/payload.rs @@ -5,7 +5,7 @@ use crate::{ }; use futures::StreamExt; use mongodb::{ - bson::{doc, from_bson, from_document, Bson}, + bson::{doc, from_document}, options::FindOptions, }; diff --git a/src/routes/channels/message_edit.rs b/src/routes/channels/message_edit.rs new file mode 100644 index 0000000000000000000000000000000000000000..ccc2f8a320fcc35efbb38730f438e449e2772edb --- /dev/null +++ b/src/routes/channels/message_edit.rs @@ -0,0 +1,58 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; + +use chrono::Utc; +use mongodb::bson::{Bson, DateTime, doc}; +use rocket_contrib::json::Json; +use serde::{Deserialize, Serialize}; +use validator::Validate; + +#[derive(Validate, Serialize, Deserialize)] +pub struct Data { + #[validate(length(min = 1, max = 2000))] + content: String, +} + +#[patch("/<target>/messages/<msg>", data = "<edit>")] +pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<()> { + edit.validate() + .map_err(|error| Error::FailedValidation { error })?; + + let channel = target.fetch_channel().await?; + + let perm = permissions::channel::calculate(&user, &channel).await; + if !perm.get_view() { + Err(Error::LabelMe)? + } + + let mut message = msg.fetch_message().await?; + if message.author != user.id { + Err(Error::CannotEditMessage)? + } + + let edited = Utc::now(); + get_collection("messages") + .update_one( + doc! { + "_id": &message.id + }, + doc! { + "$set": { + "content": &edit.content, + "edited": Bson::DateTime(edited) + } + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "message", + })?; + + message.content = edit.content.clone(); + message.edited = Some(DateTime(edited)); + message.publish_edit().await?; + + Ok(()) +} diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs index fbd9e7d0ad5f245be1a42418bd640e79e987ba94..f8885574c592ca6c209c29d3d18bee1027db8e28 100644 --- a/src/routes/channels/mod.rs +++ b/src/routes/channels/mod.rs @@ -2,6 +2,7 @@ use rocket::Route; mod delete_channel; mod fetch_channel; +mod message_edit; mod message_fetch; mod message_query; mod message_send; @@ -12,6 +13,7 @@ pub fn routes() -> Vec<Route> { delete_channel::req, message_send::req, message_query::req, - message_fetch::req + message_fetch::req, + message_edit::req ] } diff --git a/src/util/result.rs b/src/util/result.rs index 2afc5460d9a53c3478c40482030be55b74c11d7a..0f2dec321d949d3f5e96673f19ec9f3d61103408 100644 --- a/src/util/result.rs +++ b/src/util/result.rs @@ -34,6 +34,8 @@ pub enum Error { // ? Channel related errors. #[snafu(display("Already sent a message with this nonce."))] AlreadySentMessage, + #[snafu(display("Cannot edit someone else's message."))] + CannotEditMessage, // ? General errors. #[snafu(display("Failed to validate fields."))] @@ -67,6 +69,7 @@ impl<'r> Responder<'r, 'static> for Error { Error::BlockedByOther => Status::Forbidden, Error::AlreadySentMessage => Status::Conflict, + Error::CannotEditMessage => Status::Forbidden, Error::FailedValidation { .. } => Status::UnprocessableEntity, Error::DatabaseError { .. } => Status::InternalServerError,