Skip to content

Commit

Permalink
feat(core): implement message model
Browse files Browse the repository at this point in the history
closes #273
  • Loading branch information
insertish committed Aug 3, 2023
1 parent d87d608 commit 121a9cd
Show file tree
Hide file tree
Showing 14 changed files with 806 additions and 11 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 14 additions & 8 deletions crates/core/database/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,40 @@ name = "revolt-database"
version = "0.6.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = [ "Paul Makles <me@insrt.uk>" ]
authors = ["Paul Makles <me@insrt.uk>"]
description = "Revolt Backend: Database Implementation"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
# Databases
mongodb = [ "dep:mongodb", "bson" ]
mongodb = ["dep:mongodb", "bson"]

# ... Other
async-std-runtime = [ "async-std" ]
rocket-impl = [ "rocket", "schemars" ]
redis-is-patched = [ "revolt-presence/redis-is-patched" ]
async-std-runtime = ["async-std"]
rocket-impl = ["rocket", "schemars"]
redis-is-patched = ["revolt-presence/redis-is-patched"]

# Default Features
default = [ "mongodb", "async-std-runtime" ]
default = ["mongodb", "async-std-runtime"]

[dependencies]
# Core
revolt-result = { version = "0.6.5", path = "../result" }
revolt-models = { version = "0.6.5", path = "../models" }
revolt-presence = { version = "0.6.5", path = "../presence" }
revolt-permissions = { version = "0.6.5", path = "../permissions", features = [ "serde", "bson" ] }
revolt-permissions = { version = "0.6.5", path = "../permissions", features = [
"serde",
"bson",
] }

# Utility
log = "0.4"
rand = "0.8.5"
ulid = "1.0.0"
nanoid = "0.4.0"
once_cell = "1.17"
indexmap = "1.9.1"

# Serialisation
serde_json = "1"
Expand Down Expand Up @@ -61,7 +65,9 @@ async-std = { version = "1.8.0", features = ["attributes"], optional = true }

# Rocket Impl
schemars = { version = "0.8.8", optional = true }
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"], optional = true }
rocket = { version = "0.5.0-rc.2", default-features = false, features = [
"json",
], optional = true }

# Authifier
authifier = { version = "1.0" }
5 changes: 5 additions & 0 deletions crates/core/database/src/models/messages/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod model;
mod ops;

pub use model::*;
pub use ops::*;
212 changes: 212 additions & 0 deletions crates/core/database/src/models/messages/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
use indexmap::{IndexMap, IndexSet};
use iso8601_timestamp::Timestamp;
use revolt_models::v0::{Embed, MessageSort, MessageWebhook};

use crate::File;

auto_derived_partial!(
/// Message
pub struct Message {
/// Unique Id
#[serde(rename = "_id")]
pub id: String,
/// Unique value generated by client sending this message
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>,
/// Id of the channel this message was sent in
pub channel: String,
/// Id of the user or webhook that sent this message
pub author: String,
/// The webhook that sent this message
#[serde(skip_serializing_if = "Option::is_none")]
pub webhook: Option<MessageWebhook>,
/// Message content
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
/// System message
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<SystemMessage>,
/// Array of attachments
#[serde(skip_serializing_if = "Option::is_none")]
pub attachments: Option<Vec<File>>,
/// Time at which this message was last edited
#[serde(skip_serializing_if = "Option::is_none")]
pub edited: Option<Timestamp>,
/// Attached embeds to this message
#[serde(skip_serializing_if = "Option::is_none")]
pub embeds: Option<Vec<Embed>>,
/// Array of user ids mentioned in this message
#[serde(skip_serializing_if = "Option::is_none")]
pub mentions: Option<Vec<String>>,
/// Array of message ids this message is replying to
#[serde(skip_serializing_if = "Option::is_none")]
pub replies: Option<Vec<String>>,
/// Hashmap of emoji IDs to array of user IDs
#[serde(skip_serializing_if = "IndexMap::is_empty", default)]
pub reactions: IndexMap<String, IndexSet<String>>,
/// Information about how this message should be interacted with
#[serde(skip_serializing_if = "Interactions::is_default", default)]
pub interactions: Interactions,
/// Name and / or avatar overrides for this message
#[serde(skip_serializing_if = "Option::is_none")]
pub masquerade: Option<Masquerade>,
},
"PartialMessage"
);

auto_derived!(
/// System Event
#[serde(tag = "type")]
pub enum SystemMessage {
#[serde(rename = "text")]
Text { content: String },
#[serde(rename = "user_added")]
UserAdded { id: String, by: String },
#[serde(rename = "user_remove")]
UserRemove { id: String, by: String },
#[serde(rename = "user_joined")]
UserJoined { id: String },
#[serde(rename = "user_left")]
UserLeft { id: String },
#[serde(rename = "user_kicked")]
UserKicked { id: String },
#[serde(rename = "user_banned")]
UserBanned { id: String },
#[serde(rename = "channel_renamed")]
ChannelRenamed { name: String, by: String },
#[serde(rename = "channel_description_changed")]
ChannelDescriptionChanged { by: String },
#[serde(rename = "channel_icon_changed")]
ChannelIconChanged { by: String },
#[serde(rename = "channel_ownership_changed")]
ChannelOwnershipChanged { from: String, to: String },
}

/// Name and / or avatar override information
pub struct Masquerade {
/// Replace the display name shown on this message
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Replace the avatar shown on this message (URL to image file)
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<String>,
/// Replace the display role colour shown on this message
///
/// Must have `ManageRole` permission to use
#[serde(skip_serializing_if = "Option::is_none")]
pub colour: Option<String>,
}

/// Information to guide interactions on this message
#[derive(Default)]
pub struct Interactions {
/// Reactions which should always appear and be distinct
#[serde(skip_serializing_if = "Option::is_none", default)]
pub reactions: Option<IndexSet<String>>,
/// Whether reactions should be restricted to the given list
///
/// Can only be set to true if reactions list is of at least length 1
#[serde(skip_serializing_if = "crate::if_false", default)]
pub restrict_reactions: bool,
}

/// Appended Information
pub struct AppendMessage {
/// Additional embeds to include in this message
#[serde(skip_serializing_if = "Option::is_none")]
pub embeds: Option<Vec<Embed>>,
}

/// Message Time Period
///
/// Filter and sort messages by time
#[serde(untagged)]
pub enum MessageTimePeriod {
Relative {
/// Message id to search around
///
/// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
/// It will also take half of limit rounded as the limits to each side.
/// It also fetches the message ID specified.
nearby: String,
},
Absolute {
/// Message id before which messages should be fetched
before: Option<String>,
/// Message id after which messages should be fetched
after: Option<String>,
/// Message sort direction
sort: Option<MessageSort>,
},
}

/// Message Filter
pub struct MessageFilter {
/// Parent channel ID
pub channel: Option<String>,
/// Message author ID
pub author: Option<String>,
/// Search query
pub query: Option<String>,
}

/// Message Query
pub struct MessageQuery {
/// Maximum number of messages to fetch
///
/// For fetching nearby messages, this is \`(limit + 1)\`.
pub limit: Option<i64>,
/// Filter to apply
#[serde(flatten)]
pub filter: MessageFilter,
/// Time period to fetch
#[serde(flatten)]
pub time_period: MessageTimePeriod,
}
);

#[allow(clippy::disallowed_methods)]
impl Message {}

impl Interactions {
/// Validate interactions info is correct
/* pub async fn validate(
&self,
db: &Database,
permissions: &mut PermissionCalculator<'_>,
) -> Result<()> {
if let Some(reactions) = &self.reactions {
permissions.throw_permission(db, Permission::React).await?;
if reactions.len() > 20 {
return Err(Error::InvalidOperation);
}
for reaction in reactions {
if !Emoji::can_use(db, reaction).await? {
return Err(Error::InvalidOperation);
}
}
}
Ok(())
}*/

/// Check if we can use a given emoji to react
pub fn can_use(&self, emoji: &str) -> bool {
if self.restrict_reactions {
if let Some(reactions) = &self.reactions {
reactions.contains(emoji)
} else {
false
}
} else {
true
}
}

/// Check if default initialisation of fields
pub fn is_default(&self) -> bool {
!self.restrict_reactions && self.reactions.is_none()
}
}
39 changes: 39 additions & 0 deletions crates/core/database/src/models/messages/ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use revolt_result::Result;

use crate::{AppendMessage, Message, MessageQuery, PartialMessage};

// mod mongodb;
// mod reference;

#[async_trait]
pub trait AbstractMessages: Sync + Send {
/// Insert a new message into the database
async fn insert_message(&self, message: &Message) -> Result<()>;

/// Fetch a message by its id
async fn fetch_message(&self, id: &str) -> Result<Message>;

/// Fetch multiple messages by given query
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>>;

/// Update a given message with new information
async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()>;

/// Append information to a given message
async fn append_message(&self, id: &str, append: &AppendMessage) -> Result<()>;

/// Add a new reaction to a message
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;

/// Remove a reaction from a message
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;

/// Remove reaction from a message
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()>;

/// Delete a message from the database by its id
async fn delete_message(&self, id: &str) -> Result<()>;

/// Delete messages from a channel by their ids and corresponding channel id
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()>;
}
Loading

0 comments on commit 121a9cd

Please sign in to comment.