forked from jmug/stoatchat
feat(core/database): implement webhook model
This commit is contained in:
@@ -15,6 +15,7 @@ mongodb = [ "dep:mongodb", "bson" ]
|
|||||||
# ... Other
|
# ... Other
|
||||||
async-std-runtime = [ "async-std" ]
|
async-std-runtime = [ "async-std" ]
|
||||||
rocket-impl = [ "rocket", "schemars" ]
|
rocket-impl = [ "rocket", "schemars" ]
|
||||||
|
redis-is-patched = [ "revolt-presence/redis-is-patched" ]
|
||||||
|
|
||||||
# Default Features
|
# Default Features
|
||||||
default = [ "mongodb", "async-std-runtime" ]
|
default = [ "mongodb", "async-std-runtime" ]
|
||||||
@@ -22,6 +23,8 @@ default = [ "mongodb", "async-std-runtime" ]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-result = { version = "0.0.2", path = "../result" }
|
revolt-result = { version = "0.0.2", path = "../result" }
|
||||||
|
revolt-models = { version = "0.0.2", path = "../models" }
|
||||||
|
revolt-presence = { version = "0.0.2", path = "../presence" }
|
||||||
revolt-permissions = { version = "0.0.2", path = "../permissions", features = [ "serde" ] }
|
revolt-permissions = { version = "0.0.2", path = "../permissions", features = [ "serde" ] }
|
||||||
|
|
||||||
# Utility
|
# Utility
|
||||||
@@ -36,6 +39,9 @@ revolt_optional_struct = "0.2.0"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
|
iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
|
||||||
|
|
||||||
|
# Events
|
||||||
|
redis-kiss = { version = "0.1.4" }
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
bson = { optional = true, version = "2.1.0" }
|
bson = { optional = true, version = "2.1.0" }
|
||||||
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ use std::{collections::HashMap, sync::Arc};
|
|||||||
|
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
|
|
||||||
use crate::{AccountStrike, Bot, File, Member, MemberCompositeKey, Server, User, UserSettings};
|
use crate::{
|
||||||
|
AccountStrike, Bot, File, Member, MemberCompositeKey, Server, User, UserSettings, Webhook,
|
||||||
|
};
|
||||||
|
|
||||||
database_derived!(
|
database_derived!(
|
||||||
/// Reference implementation
|
/// Reference implementation
|
||||||
@@ -10,6 +12,7 @@ database_derived!(
|
|||||||
pub struct ReferenceDb {
|
pub struct ReferenceDb {
|
||||||
pub account_strikes: Arc<Mutex<HashMap<String, AccountStrike>>>,
|
pub account_strikes: Arc<Mutex<HashMap<String, AccountStrike>>>,
|
||||||
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
||||||
|
pub channel_webhooks: Arc<Mutex<HashMap<String, Webhook>>>,
|
||||||
pub user_settings: Arc<Mutex<HashMap<String, UserSettings>>>,
|
pub user_settings: Arc<Mutex<HashMap<String, UserSettings>>>,
|
||||||
pub users: Arc<Mutex<HashMap<String, User>>>,
|
pub users: Arc<Mutex<HashMap<String, User>>>,
|
||||||
pub server_members: Arc<Mutex<HashMap<MemberCompositeKey, Member>>>,
|
pub server_members: Arc<Mutex<HashMap<MemberCompositeKey, Member>>>,
|
||||||
|
|||||||
267
crates/core/database/src/events/client.rs
Normal file
267
crates/core/database/src/events/client.rs
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
use authifier::AuthifierEvent;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use revolt_models::v0::{FieldsWebhook, PartialWebhook, Webhook};
|
||||||
|
use revolt_result::Error;
|
||||||
|
|
||||||
|
use crate::Database;
|
||||||
|
|
||||||
|
/// WebSocket Client Errors
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(tag = "error")]
|
||||||
|
pub enum WebSocketError {
|
||||||
|
LabelMe,
|
||||||
|
InternalError { at: String },
|
||||||
|
InvalidSession,
|
||||||
|
OnboardingNotFinished,
|
||||||
|
AlreadyAuthenticated,
|
||||||
|
MalformedData { msg: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ping Packet
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Ping {
|
||||||
|
Binary(Vec<u8>),
|
||||||
|
Number(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Untagged Error
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum ErrorEvent {
|
||||||
|
Error(WebSocketError),
|
||||||
|
APIError(Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Protocol Events
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum EventV1 {
|
||||||
|
/// Multiple events
|
||||||
|
Bulk { v: Vec<EventV1> },
|
||||||
|
|
||||||
|
/// Successfully authenticated
|
||||||
|
Authenticated,
|
||||||
|
/* /// Basic data to cache
|
||||||
|
Ready {
|
||||||
|
users: Vec<User>,
|
||||||
|
servers: Vec<Server>,
|
||||||
|
channels: Vec<Channel>,
|
||||||
|
members: Vec<Member>,
|
||||||
|
emojis: Option<Vec<Emoji>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Ping response
|
||||||
|
Pong { data: Ping },
|
||||||
|
|
||||||
|
/// New message
|
||||||
|
Message(Message),
|
||||||
|
|
||||||
|
/// Update existing message
|
||||||
|
MessageUpdate {
|
||||||
|
id: String,
|
||||||
|
channel: String,
|
||||||
|
data: PartialMessage,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Append information to existing message
|
||||||
|
MessageAppend {
|
||||||
|
id: String,
|
||||||
|
channel: String,
|
||||||
|
append: AppendMessage,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Delete message
|
||||||
|
MessageDelete { id: String, channel: String },
|
||||||
|
|
||||||
|
/// New reaction to a message
|
||||||
|
MessageReact {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
user_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove user's reaction from message
|
||||||
|
MessageUnreact {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
user_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove a reaction from message
|
||||||
|
MessageRemoveReaction {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Bulk delete messages
|
||||||
|
BulkMessageDelete { channel: String, ids: Vec<String> },
|
||||||
|
|
||||||
|
/// New channel
|
||||||
|
ChannelCreate(Channel),
|
||||||
|
|
||||||
|
/// Update existing channel
|
||||||
|
ChannelUpdate {
|
||||||
|
id: String,
|
||||||
|
data: PartialChannel,
|
||||||
|
clear: Vec<FieldsChannel>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Delete channel
|
||||||
|
ChannelDelete { id: String },
|
||||||
|
|
||||||
|
/// User joins a group
|
||||||
|
ChannelGroupJoin { id: String, user: String },
|
||||||
|
|
||||||
|
/// User leaves a group
|
||||||
|
ChannelGroupLeave { id: String, user: String },
|
||||||
|
|
||||||
|
/// User started typing in a channel
|
||||||
|
ChannelStartTyping { id: String, user: String },
|
||||||
|
|
||||||
|
/// User stopped typing in a channel
|
||||||
|
ChannelStopTyping { id: String, user: String },
|
||||||
|
|
||||||
|
/// User acknowledged message in channel
|
||||||
|
ChannelAck {
|
||||||
|
id: String,
|
||||||
|
user: String,
|
||||||
|
message_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// New server
|
||||||
|
ServerCreate {
|
||||||
|
id: String,
|
||||||
|
server: Server,
|
||||||
|
channels: Vec<Channel>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Update existing server
|
||||||
|
ServerUpdate {
|
||||||
|
id: String,
|
||||||
|
data: PartialServer,
|
||||||
|
clear: Vec<FieldsServer>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Delete server
|
||||||
|
ServerDelete { id: String },
|
||||||
|
|
||||||
|
/// Update existing server member
|
||||||
|
ServerMemberUpdate {
|
||||||
|
id: MemberCompositeKey,
|
||||||
|
data: PartialMember,
|
||||||
|
clear: Vec<FieldsMember>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// User joins server
|
||||||
|
ServerMemberJoin { id: String, user: String },
|
||||||
|
|
||||||
|
/// User left server
|
||||||
|
ServerMemberLeave { id: String, user: String },
|
||||||
|
|
||||||
|
/// Server role created or updated
|
||||||
|
ServerRoleUpdate {
|
||||||
|
id: String,
|
||||||
|
role_id: String,
|
||||||
|
data: PartialRole,
|
||||||
|
clear: Vec<FieldsRole>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Server role deleted
|
||||||
|
ServerRoleDelete { id: String, role_id: String },
|
||||||
|
|
||||||
|
/// Update existing user
|
||||||
|
UserUpdate {
|
||||||
|
id: String,
|
||||||
|
data: PartialUser,
|
||||||
|
clear: Vec<FieldsUser>,
|
||||||
|
event_id: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Relationship with another user changed
|
||||||
|
UserRelationship {
|
||||||
|
id: String,
|
||||||
|
user: User,
|
||||||
|
// ! this field can be deprecated
|
||||||
|
status: RelationshipStatus,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Settings updated remotely
|
||||||
|
UserSettingsUpdate { id: String, update: UserSettings },
|
||||||
|
|
||||||
|
/// User has been platform banned or deleted their account
|
||||||
|
///
|
||||||
|
/// Clients should remove the following associated data:
|
||||||
|
/// - Messages
|
||||||
|
/// - DM Channels
|
||||||
|
/// - Relationships
|
||||||
|
/// - Server Memberships
|
||||||
|
///
|
||||||
|
/// User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear.
|
||||||
|
UserPlatformWipe { user_id: String, flags: i32 },
|
||||||
|
|
||||||
|
/// New emoji
|
||||||
|
EmojiCreate(Emoji),
|
||||||
|
|
||||||
|
/// Delete emoji
|
||||||
|
EmojiDelete { id: String },
|
||||||
|
|
||||||
|
/// New report
|
||||||
|
ReportCreate(Report), */
|
||||||
|
/// New webhook
|
||||||
|
WebhookCreate(Webhook),
|
||||||
|
|
||||||
|
/// Update existing webhook
|
||||||
|
WebhookUpdate {
|
||||||
|
id: String,
|
||||||
|
data: PartialWebhook,
|
||||||
|
remove: Vec<FieldsWebhook>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Delete webhook
|
||||||
|
WebhookDelete { id: String },
|
||||||
|
|
||||||
|
/// Auth events
|
||||||
|
Auth(AuthifierEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventV1 {
|
||||||
|
/// Publish helper wrapper
|
||||||
|
pub async fn p(self, channel: String) {
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
redis_kiss::p(channel, self).await;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
info!("Publishing event to {channel}: {self:?}");
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
redis_kiss::publish(channel, self).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Publish user event
|
||||||
|
pub async fn p_user(self, id: String, db: &Database) {
|
||||||
|
self.clone().p(id.clone()).await;
|
||||||
|
|
||||||
|
// ! FIXME: this should be captured by member list in the future
|
||||||
|
// ! and not immediately fanned out to users
|
||||||
|
if let Ok(members) = db.fetch_all_memberships(&id).await {
|
||||||
|
for member in members {
|
||||||
|
self.clone().p(member.id.server).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Publish private event
|
||||||
|
pub async fn private(self, id: String) {
|
||||||
|
self.p(format!("{id}!")).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Publish internal global event
|
||||||
|
pub async fn global(self) {
|
||||||
|
self.p("global".to_string()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
crates/core/database/src/events/mod.rs
Normal file
1
crates/core/database/src/events/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod client;
|
||||||
@@ -80,6 +80,8 @@ mod models;
|
|||||||
pub mod util;
|
pub mod util;
|
||||||
pub use models::*;
|
pub use models::*;
|
||||||
|
|
||||||
|
pub mod events;
|
||||||
|
|
||||||
/// Utility function to check if a boolean value is false
|
/// Utility function to check if a boolean value is false
|
||||||
pub fn if_false(t: &bool) -> bool {
|
pub fn if_false(t: &bool) -> bool {
|
||||||
!t
|
!t
|
||||||
|
|||||||
5
crates/core/database/src/models/channel_webhooks/mod.rs
Normal file
5
crates/core/database/src/models/channel_webhooks/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
158
crates/core/database/src/models/channel_webhooks/model.rs
Normal file
158
crates/core/database/src/models/channel_webhooks/model.rs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::events::client::EventV1;
|
||||||
|
use crate::{Database, File};
|
||||||
|
|
||||||
|
auto_derived_partial!(
|
||||||
|
/// Webhook
|
||||||
|
pub struct Webhook {
|
||||||
|
/// Webhook Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
|
/// The name of the webhook
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// The avatar of the webhook
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub avatar: Option<File>,
|
||||||
|
|
||||||
|
/// The channel this webhook belongs to
|
||||||
|
pub channel_id: String,
|
||||||
|
|
||||||
|
/// The private token for the webhook
|
||||||
|
pub token: Option<String>,
|
||||||
|
},
|
||||||
|
"PartialWebhook"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// Optional fields on webhook object
|
||||||
|
pub enum FieldsWebhook {
|
||||||
|
Avatar,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
impl Webhook {
|
||||||
|
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||||
|
db.insert_webhook(self).await?;
|
||||||
|
|
||||||
|
// Avoid leaking the token to people who receive the event
|
||||||
|
let mut webhook = self.clone();
|
||||||
|
webhook.token = None;
|
||||||
|
|
||||||
|
EventV1::WebhookCreate(webhook.into())
|
||||||
|
.p(self.channel_id.clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_token(&self, token: &str) -> Result<()> {
|
||||||
|
if self.token.as_deref() == Some(token) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(InvalidCredentials))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
&mut self,
|
||||||
|
db: &Database,
|
||||||
|
mut partial: PartialWebhook,
|
||||||
|
remove: Vec<FieldsWebhook>,
|
||||||
|
) -> Result<()> {
|
||||||
|
for field in &remove {
|
||||||
|
self.remove_field(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.apply_options(partial.clone());
|
||||||
|
|
||||||
|
db.update_webhook(&self.id, &partial, &remove).await?;
|
||||||
|
|
||||||
|
partial.token = None; // Avoid leaking the token to people who receive the event
|
||||||
|
|
||||||
|
EventV1::WebhookUpdate {
|
||||||
|
id: self.id.clone(),
|
||||||
|
data: partial.into(),
|
||||||
|
remove: remove.into_iter().map(|v| v.into()).collect(),
|
||||||
|
}
|
||||||
|
.p(self.channel_id.clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_field(&mut self, field: &FieldsWebhook) {
|
||||||
|
match field {
|
||||||
|
FieldsWebhook::Avatar => self.avatar = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||||
|
db.delete_webhook(&self.id).await?;
|
||||||
|
|
||||||
|
EventV1::WebhookDelete {
|
||||||
|
id: self.id.clone(),
|
||||||
|
}
|
||||||
|
.p(self.channel_id.clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{FieldsWebhook, PartialWebhook, Webhook};
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn crud() {
|
||||||
|
database_test!(|db| async move {
|
||||||
|
let webhook_id = "webhook";
|
||||||
|
let channel_id = "channel";
|
||||||
|
|
||||||
|
let webhook = Webhook {
|
||||||
|
id: webhook_id.to_string(),
|
||||||
|
name: "Webhook Name".to_string(),
|
||||||
|
channel_id: channel_id.to_string(),
|
||||||
|
avatar: Some(Default::default()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
db.insert_webhook(&webhook).await.unwrap();
|
||||||
|
|
||||||
|
let mut updated_webhook = webhook.clone();
|
||||||
|
updated_webhook
|
||||||
|
.update(
|
||||||
|
&db,
|
||||||
|
PartialWebhook {
|
||||||
|
name: Some("New Name".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
vec![FieldsWebhook::Avatar],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fetched_webhook = db.fetch_webhook(webhook_id).await.unwrap();
|
||||||
|
let fetched_webhooks = db.fetch_webhooks_for_channel(channel_id).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated_webhook, fetched_webhook);
|
||||||
|
assert_ne!(webhook, fetched_webhook);
|
||||||
|
assert_eq!(1, fetched_webhooks.len());
|
||||||
|
assert_eq!(fetched_webhook, fetched_webhooks[0]);
|
||||||
|
|
||||||
|
webhook.delete(&db).await.unwrap();
|
||||||
|
assert!(db.fetch_webhook(webhook_id).await.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
db.fetch_webhooks_for_channel(channel_id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.len()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
29
crates/core/database/src/models/channel_webhooks/ops.rs
Normal file
29
crates/core/database/src/models/channel_webhooks/ops.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::{FieldsWebhook, PartialWebhook, Webhook};
|
||||||
|
|
||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractWebhooks: Sync + Send {
|
||||||
|
/// Insert new webhook into the database
|
||||||
|
async fn insert_webhook(&self, webhook: &Webhook) -> Result<()>;
|
||||||
|
|
||||||
|
/// Fetch webhook by id
|
||||||
|
async fn fetch_webhook(&self, webhook_id: &str) -> Result<Webhook>;
|
||||||
|
|
||||||
|
/// Fetch webhooks for channel
|
||||||
|
async fn fetch_webhooks_for_channel(&self, channel_id: &str) -> Result<Vec<Webhook>>;
|
||||||
|
|
||||||
|
/// Update webhook with new information
|
||||||
|
async fn update_webhook(
|
||||||
|
&self,
|
||||||
|
webhook_id: &str,
|
||||||
|
partial: &PartialWebhook,
|
||||||
|
remove: &[FieldsWebhook],
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
/// Delete webhook by id
|
||||||
|
async fn delete_webhook(&self, webhook_id: &str) -> Result<()>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
use futures::StreamExt;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::{FieldsWebhook, PartialWebhook, Webhook};
|
||||||
|
use crate::{IntoDocumentPath, MongoDb};
|
||||||
|
|
||||||
|
use super::AbstractWebhooks;
|
||||||
|
|
||||||
|
static COL: &str = "channel_webhooks";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractWebhooks for MongoDb {
|
||||||
|
/// Insert new webhook into the database
|
||||||
|
async fn insert_webhook(&self, webhook: &Webhook) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, &webhook).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch webhook by id
|
||||||
|
async fn fetch_webhook(&self, webhook_id: &str) -> Result<Webhook> {
|
||||||
|
query!(self, find_one_by_id, COL, webhook_id)?.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch webhooks for channel
|
||||||
|
async fn fetch_webhooks_for_channel(&self, channel_id: &str) -> Result<Vec<Webhook>> {
|
||||||
|
Ok(self
|
||||||
|
.col::<Webhook>(COL)
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"channel_id": channel_id,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| create_database_error!("find", COL))?
|
||||||
|
.filter_map(|s| async {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
Some(s.unwrap())
|
||||||
|
} else {
|
||||||
|
s.ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update webhook with new information
|
||||||
|
async fn update_webhook(
|
||||||
|
&self,
|
||||||
|
webhook_id: &str,
|
||||||
|
partial: &PartialWebhook,
|
||||||
|
remove: &[FieldsWebhook],
|
||||||
|
) -> Result<()> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
update_one_by_id,
|
||||||
|
COL,
|
||||||
|
webhook_id,
|
||||||
|
partial,
|
||||||
|
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete webhook by id
|
||||||
|
async fn delete_webhook(&self, webhook_id: &str) -> Result<()> {
|
||||||
|
query!(self, delete_one_by_id, COL, webhook_id).map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDocumentPath for FieldsWebhook {
|
||||||
|
fn as_path(&self) -> Option<&'static str> {
|
||||||
|
Some(match self {
|
||||||
|
FieldsWebhook::Avatar => "avatar",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::{FieldsWebhook, PartialWebhook, Webhook};
|
||||||
|
|
||||||
|
use super::AbstractWebhooks;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractWebhooks for ReferenceDb {
|
||||||
|
/// Insert new webhook into the database
|
||||||
|
async fn insert_webhook(&self, webhook: &Webhook) -> Result<()> {
|
||||||
|
let mut webhooks = self.channel_webhooks.lock().await;
|
||||||
|
if webhooks.contains_key(&webhook.id) {
|
||||||
|
Err(create_database_error!("insert", "webhook"))
|
||||||
|
} else {
|
||||||
|
webhooks.insert(webhook.id.to_string(), webhook.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch webhook by id
|
||||||
|
async fn fetch_webhook(&self, webhook_id: &str) -> Result<Webhook> {
|
||||||
|
let webhooks = self.channel_webhooks.lock().await;
|
||||||
|
webhooks
|
||||||
|
.get(webhook_id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch webhooks for channel
|
||||||
|
async fn fetch_webhooks_for_channel(&self, channel_id: &str) -> Result<Vec<Webhook>> {
|
||||||
|
let webhooks = self.channel_webhooks.lock().await;
|
||||||
|
Ok(webhooks
|
||||||
|
.values()
|
||||||
|
.filter(|webhook| webhook.channel_id == channel_id)
|
||||||
|
.cloned()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update webhook with new information
|
||||||
|
async fn update_webhook(
|
||||||
|
&self,
|
||||||
|
webhook_id: &str,
|
||||||
|
partial: &PartialWebhook,
|
||||||
|
remove: &[FieldsWebhook],
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut webhooks = self.channel_webhooks.lock().await;
|
||||||
|
if let Some(webhook) = webhooks.get_mut(webhook_id) {
|
||||||
|
for field in remove {
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
webhook.remove_field(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook.apply_options(partial.clone());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete webhook by id
|
||||||
|
async fn delete_webhook(&self, webhook_id: &str) -> Result<()> {
|
||||||
|
let mut webhooks = self.channel_webhooks.lock().await;
|
||||||
|
if webhooks.remove(webhook_id).is_some() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod admin_migrations;
|
mod admin_migrations;
|
||||||
mod bots;
|
mod bots;
|
||||||
|
mod channel_webhooks;
|
||||||
mod files;
|
mod files;
|
||||||
mod safety_strikes;
|
mod safety_strikes;
|
||||||
mod server_members;
|
mod server_members;
|
||||||
@@ -9,6 +10,7 @@ mod users;
|
|||||||
|
|
||||||
pub use admin_migrations::*;
|
pub use admin_migrations::*;
|
||||||
pub use bots::*;
|
pub use bots::*;
|
||||||
|
pub use channel_webhooks::*;
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
pub use safety_strikes::*;
|
pub use safety_strikes::*;
|
||||||
pub use server_members::*;
|
pub use server_members::*;
|
||||||
@@ -29,6 +31,7 @@ pub trait AbstractDatabase:
|
|||||||
+ servers::AbstractServers
|
+ servers::AbstractServers
|
||||||
+ user_settings::AbstractUserSettings
|
+ user_settings::AbstractUserSettings
|
||||||
+ users::AbstractUsers
|
+ users::AbstractUsers
|
||||||
|
+ channel_webhooks::AbstractWebhooks
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
crates/core/database/src/util/bridge/mod.rs
Normal file
1
crates/core/database/src/util/bridge/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod v0;
|
||||||
217
crates/core/database/src/util/bridge/v0.rs
Normal file
217
crates/core/database/src/util/bridge/v0.rs
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
use revolt_models::v0::*;
|
||||||
|
|
||||||
|
impl From<crate::AccountStrike> for AccountStrike {
|
||||||
|
fn from(value: crate::AccountStrike) -> Self {
|
||||||
|
AccountStrike {
|
||||||
|
id: value.id,
|
||||||
|
user_id: value.user_id,
|
||||||
|
reason: value.reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::Bot {
|
||||||
|
pub fn into_public_bot(self, user: crate::User) -> PublicBot {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
assert_eq!(self.id, user.id);
|
||||||
|
|
||||||
|
PublicBot {
|
||||||
|
id: self.id,
|
||||||
|
username: user.username,
|
||||||
|
avatar: user.avatar.map(|x| x.id).unwrap_or_default(),
|
||||||
|
description: user
|
||||||
|
.profile
|
||||||
|
.map(|profile| profile.content)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::Bot> for Bot {
|
||||||
|
fn from(value: crate::Bot) -> Self {
|
||||||
|
Bot {
|
||||||
|
id: value.id,
|
||||||
|
owner_id: value.owner,
|
||||||
|
token: value.token,
|
||||||
|
public: value.public,
|
||||||
|
analytics: value.analytics,
|
||||||
|
discoverable: value.discoverable,
|
||||||
|
interactions_url: value.interactions_url,
|
||||||
|
terms_of_service_url: value.terms_of_service_url,
|
||||||
|
privacy_policy_url: value.privacy_policy_url,
|
||||||
|
flags: value.flags.unwrap_or_default() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::Webhook> for Webhook {
|
||||||
|
fn from(value: crate::Webhook) -> Self {
|
||||||
|
Webhook {
|
||||||
|
id: value.id,
|
||||||
|
name: value.name,
|
||||||
|
avatar: value.avatar.map(|file| file.into()),
|
||||||
|
channel_id: value.channel_id,
|
||||||
|
token: value.token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::PartialWebhook> for PartialWebhook {
|
||||||
|
fn from(value: crate::PartialWebhook) -> Self {
|
||||||
|
PartialWebhook {
|
||||||
|
id: value.id,
|
||||||
|
name: value.name,
|
||||||
|
avatar: value.avatar.map(|file| file.into()),
|
||||||
|
channel_id: value.channel_id,
|
||||||
|
token: value.token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FieldsWebhook> for crate::FieldsWebhook {
|
||||||
|
fn from(_value: FieldsWebhook) -> Self {
|
||||||
|
Self::Avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::FieldsWebhook> for FieldsWebhook {
|
||||||
|
fn from(_value: crate::FieldsWebhook) -> Self {
|
||||||
|
Self::Avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::File> for File {
|
||||||
|
fn from(value: crate::File) -> Self {
|
||||||
|
File {
|
||||||
|
id: value.id,
|
||||||
|
tag: value.tag,
|
||||||
|
filename: value.filename,
|
||||||
|
metadata: value.metadata.into(),
|
||||||
|
content_type: value.content_type,
|
||||||
|
size: value.size,
|
||||||
|
deleted: value.deleted,
|
||||||
|
reported: value.reported,
|
||||||
|
message_id: value.message_id,
|
||||||
|
user_id: value.user_id,
|
||||||
|
server_id: value.server_id,
|
||||||
|
object_id: value.object_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::Metadata> for Metadata {
|
||||||
|
fn from(value: crate::Metadata) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::Metadata::File => Metadata::File,
|
||||||
|
crate::Metadata::Text => Metadata::Text,
|
||||||
|
crate::Metadata::Image { width, height } => Metadata::Image {
|
||||||
|
width: width as usize,
|
||||||
|
height: height as usize,
|
||||||
|
},
|
||||||
|
crate::Metadata::Video { width, height } => Metadata::Video {
|
||||||
|
width: width as usize,
|
||||||
|
height: height as usize,
|
||||||
|
},
|
||||||
|
crate::Metadata::Audio => Metadata::Audio,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::User {
|
||||||
|
pub async fn into<P>(self, perspective: P) -> User
|
||||||
|
where
|
||||||
|
P: Into<Option<crate::User>>,
|
||||||
|
{
|
||||||
|
let relationship = if let Some(perspective) = perspective.into() {
|
||||||
|
perspective
|
||||||
|
.relations
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.find(|relationship| relationship.id == self.id)
|
||||||
|
.map(|relationship| relationship.status.into())
|
||||||
|
.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
RelationshipStatus::None
|
||||||
|
};
|
||||||
|
|
||||||
|
// do permission stuff here
|
||||||
|
// TODO: implement permissions =)
|
||||||
|
let can_see_profile = false;
|
||||||
|
|
||||||
|
User {
|
||||||
|
username: self.username,
|
||||||
|
avatar: self.avatar.map(|file| file.into()),
|
||||||
|
relations: vec![],
|
||||||
|
badges: self.badges.unwrap_or_default() as u32,
|
||||||
|
status: None,
|
||||||
|
profile: None,
|
||||||
|
flags: self.flags.unwrap_or_default() as u32,
|
||||||
|
privileged: self.privileged,
|
||||||
|
bot: self.bot.map(|bot| bot.into()),
|
||||||
|
relationship,
|
||||||
|
online: can_see_profile && revolt_presence::is_online(&self.id).await,
|
||||||
|
id: self.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::RelationshipStatus> for RelationshipStatus {
|
||||||
|
fn from(value: crate::RelationshipStatus) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::RelationshipStatus::None => RelationshipStatus::None,
|
||||||
|
crate::RelationshipStatus::User => RelationshipStatus::User,
|
||||||
|
crate::RelationshipStatus::Friend => RelationshipStatus::Friend,
|
||||||
|
crate::RelationshipStatus::Outgoing => RelationshipStatus::Outgoing,
|
||||||
|
crate::RelationshipStatus::Incoming => RelationshipStatus::Incoming,
|
||||||
|
crate::RelationshipStatus::Blocked => RelationshipStatus::Blocked,
|
||||||
|
crate::RelationshipStatus::BlockedOther => RelationshipStatus::BlockedOther,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::Relationship> for Relationship {
|
||||||
|
fn from(value: crate::Relationship) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: value.id,
|
||||||
|
status: value.status.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::Presence> for Presence {
|
||||||
|
fn from(value: crate::Presence) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::Presence::Online => Presence::Online,
|
||||||
|
crate::Presence::Idle => Presence::Idle,
|
||||||
|
crate::Presence::Focus => Presence::Focus,
|
||||||
|
crate::Presence::Busy => Presence::Busy,
|
||||||
|
crate::Presence::Invisible => Presence::Invisible,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::UserStatus> for UserStatus {
|
||||||
|
fn from(value: crate::UserStatus) -> Self {
|
||||||
|
UserStatus {
|
||||||
|
text: value.text,
|
||||||
|
presence: value.presence.map(|presence| presence.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::UserProfile> for UserProfile {
|
||||||
|
fn from(value: crate::UserProfile) -> Self {
|
||||||
|
UserProfile {
|
||||||
|
content: value.content,
|
||||||
|
background: value.background.map(|file| file.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::BotInformation> for BotInformation {
|
||||||
|
fn from(value: crate::BotInformation) -> Self {
|
||||||
|
BotInformation {
|
||||||
|
owner_id: value.owner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
pub mod bridge;
|
||||||
pub mod permissions;
|
pub mod permissions;
|
||||||
pub mod reference;
|
pub mod reference;
|
||||||
|
|||||||
Reference in New Issue
Block a user