forked from jmug/stoatchat
feat: Message pinning
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ pub enum ErrorType {
|
|||||||
},
|
},
|
||||||
AlreadyInGroup,
|
AlreadyInGroup,
|
||||||
NotInGroup,
|
NotInGroup,
|
||||||
|
AlreadyPinned,
|
||||||
|
NotPinned,
|
||||||
|
|
||||||
// ? Server related errors
|
// ? Server related errors
|
||||||
UnknownServer,
|
UnknownServer,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
39
crates/delta/src/routes/channels/message_pin.rs
Normal file
39
crates/delta/src/routes/channels/message_pin.rs
Normal 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)
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
39
crates/delta/src/routes/channels/message_unpin.rs
Normal file
39
crates/delta/src/routes/channels/message_unpin.rs
Normal 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)
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user