feat: Message pinning

This commit is contained in:
Zomatree
2024-07-18 18:37:41 +01:00
parent 412f4a99d7
commit 389ecc0e5c
13 changed files with 136 additions and 2 deletions

View File

@@ -87,3 +87,8 @@ pub mod tasks;
pub fn if_false(t: &bool) -> bool { pub fn if_false(t: &bool) -> bool {
!t !t
} }
/// Utility function to check if an option doesnt contain true
pub fn if_option_false(t: &Option<bool>) -> bool {
t != &Some(true)
}

View File

@@ -65,6 +65,9 @@ auto_derived_partial!(
/// Name and / or avatar overrides for this message /// Name and / or avatar overrides for this message
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub masquerade: Option<Masquerade>, pub masquerade: Option<Masquerade>,
/// Whether or not the message in pinned
#[serde(skip_serializing_if = "crate::if_option_false")]
pub pinned: Option<bool>,
/// Bitfield of message flags /// Bitfield of message flags
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -168,6 +171,8 @@ auto_derived!(
pub author: Option<String>, pub author: Option<String>,
/// Search query /// Search query
pub query: Option<String>, pub query: Option<String>,
/// Search for pinned
pub pinned: Option<bool>,
} }
/// Message Query /// Message Query
@@ -205,6 +210,7 @@ impl Default for Message {
interactions: Default::default(), interactions: Default::default(),
masquerade: None, masquerade: None,
flags: None, flags: None,
pinned: None,
} }
} }
} }
@@ -571,6 +577,8 @@ impl Message {
users.push(id.clone()); users.push(id.clone());
} }
v0::SystemMessage::Text { .. } => {} v0::SystemMessage::Text { .. } => {}
v0::SystemMessage::MessagePinned { .. } => {},
v0::SystemMessage::MessageUnpinned { .. } => {},
} }
} }
users users

View File

@@ -50,6 +50,10 @@ impl AbstractMessages for MongoDb {
false false
}; };
if let Some(pinned) = query.filter.pinned {
filter.insert("pinned", pinned);
};
// 2. Find query limit // 2. Find query limit
let limit = query.limit.unwrap_or(50); let limit = query.limit.unwrap_or(50);
@@ -156,6 +160,7 @@ impl AbstractMessages for MongoDb {
.build(), .build(),
) )
.await .await
.inspect_err(|e| eprintln!("{e:?}"))
.map_err(|_| create_database_error!("find", COL)) .map_err(|_| create_database_error!("find", COL))
} }
} }

View File

@@ -56,6 +56,12 @@ impl AbstractMessages for ReferenceDb {
} }
} }
if let Some(pinned) = query.filter.pinned {
if message.pinned.unwrap_or_default() == pinned {
return false
}
}
true true
}) })
.cloned() .cloned()

View File

@@ -482,6 +482,7 @@ impl crate::Message {
interactions: self.interactions.into(), interactions: self.interactions.into(),
masquerade: self.masquerade.map(Into::into), masquerade: self.masquerade.map(Into::into),
flags: self.flags.map(|flags| flags as u32).unwrap_or_default(), flags: self.flags.map(|flags| flags as u32).unwrap_or_default(),
pinned: self.pinned,
} }
} }
} }
@@ -509,6 +510,7 @@ impl From<crate::PartialMessage> for PartialMessage {
interactions: value.interactions.map(Into::into), interactions: value.interactions.map(Into::into),
masquerade: value.masquerade.map(Into::into), masquerade: value.masquerade.map(Into::into),
flags: value.flags.map(|flags| flags as u32), flags: value.flags.map(|flags| flags as u32),
pinned: value.pinned,
} }
} }
} }

View File

@@ -66,3 +66,8 @@ pub fn if_false(t: &bool) -> bool {
pub fn if_zero_u32(t: &u32) -> bool { pub fn if_zero_u32(t: &u32) -> bool {
t == &0 t == &0
} }
/// Utility function to check if an option doesnt contain true
pub fn if_option_false(t: &Option<bool>) -> bool {
t != &Some(true)
}

View File

@@ -70,6 +70,9 @@ auto_derived_partial!(
/// Name and / or avatar overrides for this message /// Name and / or avatar overrides for this message
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub masquerade: Option<Masquerade>, pub masquerade: Option<Masquerade>,
/// Whether or not the message in pinned
#[serde(skip_serializing_if = "crate::if_option_false")]
pub pinned: Option<bool>,
/// Bitfield of message flags /// Bitfield of message flags
/// ///
@@ -127,6 +130,10 @@ auto_derived!(
ChannelIconChanged { by: String }, ChannelIconChanged { by: String },
#[serde(rename = "channel_ownership_changed")] #[serde(rename = "channel_ownership_changed")]
ChannelOwnershipChanged { from: String, to: String }, ChannelOwnershipChanged { from: String, to: String },
#[serde(rename = "message_pinned")]
MessagePinned { id: String },
#[serde(rename = "message_unpinned")]
MessageUnpinned { id: String },
} }
/// Name and / or avatar override information /// Name and / or avatar override information
@@ -298,7 +305,9 @@ auto_derived!(
/// ///
/// See [MongoDB documentation](https://docs.mongodb.com/manual/text-search/#-text-operator) for more information. /// See [MongoDB documentation](https://docs.mongodb.com/manual/text-search/#-text-operator) for more information.
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))] #[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
pub query: String, pub query: Option<String>,
/// Whether to only search for pinned messages, cannot be sent with `query`.
pub pinned: Option<bool>,
/// Maximum number of messages to fetch /// Maximum number of messages to fetch
#[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))] #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
@@ -418,6 +427,8 @@ impl From<SystemMessage> for String {
SystemMessage::ChannelOwnershipChanged { .. } => { SystemMessage::ChannelOwnershipChanged { .. } => {
"Channel ownership changed.".to_string() "Channel ownership changed.".to_string()
} }
SystemMessage::MessagePinned { .. } => "Message pinned.".to_string(),
SystemMessage::MessageUnpinned { .. } => "Message unpinned.".to_string(),
} }
} }
} }

View File

@@ -90,6 +90,8 @@ pub enum ErrorType {
}, },
AlreadyInGroup, AlreadyInGroup,
NotInGroup, NotInGroup,
AlreadyPinned,
NotPinned,
// ? Server related errors // ? Server related errors
UnknownServer, UnknownServer,

View File

@@ -40,6 +40,8 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::GroupTooLarge { .. } => Status::Forbidden, ErrorType::GroupTooLarge { .. } => Status::Forbidden,
ErrorType::AlreadyInGroup => Status::Conflict, ErrorType::AlreadyInGroup => Status::Conflict,
ErrorType::NotInGroup => Status::NotFound, ErrorType::NotInGroup => Status::NotFound,
ErrorType::AlreadyPinned => Status::BadRequest,
ErrorType::NotPinned => Status::BadRequest,
ErrorType::UnknownServer => Status::NotFound, ErrorType::UnknownServer => Status::NotFound,
ErrorType::InvalidRole => Status::NotFound, ErrorType::InvalidRole => Status::NotFound,

View File

@@ -0,0 +1,39 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, PartialMessage, User
};
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// # Pins a message
///
/// Pins a message by its id.
#[openapi(tag = "Messaging")]
#[post("/<target>/messages/<msg>/pin")]
pub async fn message_pin(
db: &State<Database>,
user: User,
target: Reference,
msg: Reference,
) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?;
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
calculate_channel_permissions(&mut query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::ManageMessages)?;
let mut message = msg.as_message_in_channel(db, &channel.id()).await?;
if message.pinned.unwrap_or_default() {
return Err(create_error!(AlreadyPinned))
}
message.update(db, PartialMessage {
pinned: Some(true),
..Default::default()
}).await?;
Ok(EmptyResponse)
}

View File

@@ -30,6 +30,10 @@ pub async fn search(
}) })
})?; })?;
if options.query.is_some() && options.pinned.is_some() {
return Err(create_error!(InvalidOperation))
}
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
@@ -39,6 +43,7 @@ pub async fn search(
let v0::DataMessageSearch { let v0::DataMessageSearch {
query, query,
pinned,
limit, limit,
before, before,
after, after,
@@ -51,7 +56,8 @@ pub async fn search(
MessageQuery { MessageQuery {
filter: MessageFilter { filter: MessageFilter {
channel: Some(channel.id().to_string()), channel: Some(channel.id().to_string()),
query: Some(query), query,
pinned,
..Default::default() ..Default::default()
}, },
time_period: MessageTimePeriod::Absolute { time_period: MessageTimePeriod::Absolute {

View File

@@ -0,0 +1,39 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, PartialMessage, User
};
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// # Unpins a message
///
/// Unpins a message by its id.
#[openapi(tag = "Messaging")]
#[post("/<target>/messages/<msg>/unpin")]
pub async fn message_unpin(
db: &State<Database>,
user: User,
target: Reference,
msg: Reference,
) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?;
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
calculate_channel_permissions(&mut query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::ManageMessages)?;
let mut message = msg.as_message_in_channel(db, &channel.id()).await?;
if !message.pinned.unwrap_or_default() {
return Err(create_error!(NotPinned))
}
message.update(db, PartialMessage {
pinned: Some(false),
..Default::default()
}).await?;
Ok(EmptyResponse)
}

View File

@@ -15,10 +15,12 @@ mod message_clear_reactions;
mod message_delete; mod message_delete;
mod message_edit; mod message_edit;
mod message_fetch; mod message_fetch;
mod message_pin;
mod message_query; mod message_query;
mod message_react; mod message_react;
mod message_search; mod message_search;
mod message_send; mod message_send;
mod message_unpin;
mod message_unreact; mod message_unreact;
mod permissions_set; mod permissions_set;
mod permissions_set_default; mod permissions_set_default;
@@ -37,10 +39,12 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
message_send::message_send, message_send::message_send,
message_query::query, message_query::query,
message_search::search, message_search::search,
message_pin::message_pin,
message_fetch::fetch, message_fetch::fetch,
message_edit::edit, message_edit::edit,
message_bulk_delete::bulk_delete_messages, message_bulk_delete::bulk_delete_messages,
message_delete::delete, message_delete::delete,
message_unpin::message_unpin,
group_create::create_group, group_create::create_group,
group_add_member::add_member, group_add_member::add_member,
group_remove_member::remove_member, group_remove_member::remove_member,