Events: Distribute user updates to servers.
Fix: Deleting a server now correctly deletes all associated objects.
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export version=0.5.1-alpha.16
|
export version=0.5.1-alpha.17
|
||||||
echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
|
echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::database::*;
|
|||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use mongodb::bson::Bson;
|
||||||
use mongodb::{
|
use mongodb::{
|
||||||
bson::{doc, to_document, Document},
|
bson::{doc, to_document, Document},
|
||||||
options::FindOptions,
|
options::FindOptions,
|
||||||
@@ -150,11 +151,7 @@ impl Channel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self) -> Result<()> {
|
pub async fn delete_associated_objects(id: Bson) -> Result<()> {
|
||||||
let id = self.id();
|
|
||||||
let messages = get_collection("messages");
|
|
||||||
|
|
||||||
// Delete any invites.
|
|
||||||
get_collection("channel_invites")
|
get_collection("channel_invites")
|
||||||
.delete_many(
|
.delete_many(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -163,87 +160,103 @@ impl Channel {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.map(|_| ())
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "delete_many",
|
operation: "delete_many",
|
||||||
with: "channel_invites",
|
with: "channel_invites",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_messages(id: Bson) -> Result<()> {
|
||||||
|
let messages = get_collection("messages");
|
||||||
|
|
||||||
|
// Delete any unreads.
|
||||||
|
get_collection("channel_unreads")
|
||||||
|
.delete_many(
|
||||||
|
doc! {
|
||||||
|
"_id.channel": &id
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "delete_many",
|
||||||
|
with: "channel_unreads",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Check if there are any attachments we need to delete.
|
||||||
|
let message_ids = messages
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"channel": &id,
|
||||||
|
"attachment": {
|
||||||
|
"$exists": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "fetch_many",
|
||||||
|
with: "messages",
|
||||||
|
})?
|
||||||
|
.filter_map(async move |s| s.ok())
|
||||||
|
.collect::<Vec<Document>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
// If we found any, mark them as deleted.
|
||||||
|
if message_ids.len() > 0 {
|
||||||
|
get_collection("attachments")
|
||||||
|
.update_many(
|
||||||
|
doc! {
|
||||||
|
"message_id": {
|
||||||
|
"$in": message_ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"deleted": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_many",
|
||||||
|
with: "attachments",
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And then delete said messages.
|
||||||
|
messages
|
||||||
|
.delete_many(
|
||||||
|
doc! {
|
||||||
|
"channel": id
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "delete_many",
|
||||||
|
with: "messages",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self) -> Result<()> {
|
||||||
|
let id = self.id();
|
||||||
|
|
||||||
|
// Delete any invites.
|
||||||
|
Channel::delete_associated_objects(Bson::String(id.to_string())).await?;
|
||||||
|
|
||||||
|
// Delete messages.
|
||||||
match &self {
|
match &self {
|
||||||
Channel::VoiceChannel { .. } => {},
|
Channel::VoiceChannel { .. } => {},
|
||||||
_ => {
|
_ => {
|
||||||
// Delete any unreads.
|
Channel::delete_messages(Bson::String(id.to_string())).await?;
|
||||||
get_collection("channel_unreads")
|
|
||||||
.delete_many(
|
|
||||||
doc! {
|
|
||||||
"_id.channel": id
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "delete_many",
|
|
||||||
with: "channel_unreads",
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Check if there are any attachments we need to delete.
|
|
||||||
let message_ids = messages
|
|
||||||
.find(
|
|
||||||
doc! {
|
|
||||||
"channel": id,
|
|
||||||
"attachment": {
|
|
||||||
"$exists": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "fetch_many",
|
|
||||||
with: "messages",
|
|
||||||
})?
|
|
||||||
.filter_map(async move |s| s.ok())
|
|
||||||
.collect::<Vec<Document>>()
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
// If we found any, mark them as deleted.
|
|
||||||
if message_ids.len() > 0 {
|
|
||||||
get_collection("attachments")
|
|
||||||
.update_many(
|
|
||||||
doc! {
|
|
||||||
"message_id": {
|
|
||||||
"$in": message_ids
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doc! {
|
|
||||||
"$set": {
|
|
||||||
"deleted": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_many",
|
|
||||||
with: "attachments",
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// And then delete said messages.
|
|
||||||
messages
|
|
||||||
.delete_many(
|
|
||||||
doc! {
|
|
||||||
"channel": id
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "delete_many",
|
|
||||||
with: "messages",
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ use crate::database::*;
|
|||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::{Bson, doc};
|
||||||
use mongodb::bson::from_document;
|
use mongodb::bson::from_document;
|
||||||
use mongodb::bson::to_document;
|
use mongodb::bson::to_document;
|
||||||
use mongodb::bson::Document;
|
use mongodb::bson::Document;
|
||||||
use mongodb::options::FindOptions;
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket_contrib::json::JsonValue;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -133,58 +132,11 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self) -> Result<()> {
|
pub async fn delete(&self) -> Result<()> {
|
||||||
let messages = get_collection("messages");
|
|
||||||
|
|
||||||
// Check if there are any attachments we need to delete.
|
// Check if there are any attachments we need to delete.
|
||||||
// ! FIXME: make this generic and merge with channel delete
|
Channel::delete_messages(Bson::Document(doc! { "$in": &self.channels })).await?;
|
||||||
// ! e.g. delete_channel(filter: doc!)
|
|
||||||
let message_ids = messages
|
|
||||||
.find(
|
|
||||||
doc! {
|
|
||||||
"server": &self.id,
|
|
||||||
"attachment": {
|
|
||||||
"$exists": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "fetch_many",
|
|
||||||
with: "messages",
|
|
||||||
})?
|
|
||||||
.filter_map(async move |s| s.ok())
|
|
||||||
.collect::<Vec<Document>>()
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
// If we found any, mark them as deleted.
|
// Delete all channels.
|
||||||
if message_ids.len() > 0 {
|
get_collection("channels")
|
||||||
get_collection("attachments")
|
|
||||||
.update_many(
|
|
||||||
doc! {
|
|
||||||
"message_id": {
|
|
||||||
"$in": message_ids
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doc! {
|
|
||||||
"$set": {
|
|
||||||
"deleted": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_many",
|
|
||||||
with: "attachments",
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// And then delete said messages.
|
|
||||||
messages
|
|
||||||
.delete_many(
|
.delete_many(
|
||||||
doc! {
|
doc! {
|
||||||
"server": &self.id
|
"server": &self.id
|
||||||
@@ -194,27 +146,13 @@ impl Server {
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "delete_many",
|
operation: "delete_many",
|
||||||
with: "messages",
|
with: "channels",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Delete all channels, members, bans and invites.
|
// Delete any associated objects, e.g. unreads and invites.
|
||||||
for with in &["channels", "channel_invites"] {
|
Channel::delete_associated_objects(Bson::Document(doc! { "$in": &self.channels })).await?;
|
||||||
get_collection(with)
|
|
||||||
.delete_many(
|
|
||||||
doc! {
|
|
||||||
"server": &self.id
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "delete_many",
|
|
||||||
with,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ! FIXME: delete any unreads
|
|
||||||
|
|
||||||
|
// Delete members and bans.
|
||||||
for with in &["server_members", "server_bans"] {
|
for with in &["server_members", "server_bans"] {
|
||||||
get_collection(with)
|
get_collection(with)
|
||||||
.delete_many(
|
.delete_many(
|
||||||
@@ -230,6 +168,16 @@ impl Server {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete server icon / banner.
|
||||||
|
if let Some(attachment) = &self.icon {
|
||||||
|
attachment.delete().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(attachment) = &self.banner {
|
||||||
|
attachment.delete().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the server
|
||||||
get_collection("servers")
|
get_collection("servers")
|
||||||
.delete_one(
|
.delete_one(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -248,14 +196,6 @@ impl Server {
|
|||||||
}
|
}
|
||||||
.publish(self.id.clone());
|
.publish(self.id.clone());
|
||||||
|
|
||||||
if let Some(attachment) = &self.icon {
|
|
||||||
attachment.delete().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(attachment) = &self.banner {
|
|
||||||
attachment.delete().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -229,11 +229,11 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Utility function to get all the server IDs the user is in.
|
/// Utility function to get all the server IDs the user is in.
|
||||||
pub async fn fetch_server_ids(&self) -> Result<Vec<String>> {
|
pub async fn fetch_server_ids(id: &str) -> Result<Vec<String>> {
|
||||||
Ok(get_collection("server_members")
|
Ok(get_collection("server_members")
|
||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
"_id.user": &self.id
|
"_id.user": id
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -256,11 +256,11 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Utility function to fetch unread objects for user.
|
/// Utility function to fetch unread objects for user.
|
||||||
pub async fn fetch_unreads(&self) -> Result<Vec<Document>> {
|
pub async fn fetch_unreads(id: &str) -> Result<Vec<Document>> {
|
||||||
Ok(get_collection("channel_unreads")
|
Ok(get_collection("channel_unreads")
|
||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
"_id.user": &self.id
|
"_id.user": id
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let check_server_overlap = async || {
|
let check_server_overlap = async || {
|
||||||
let server_ids = self.perspective.fetch_server_ids().await?;
|
let server_ids = User::fetch_server_ids(&self.perspective.id).await?;
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
get_collection("server_members")
|
get_collection("server_members")
|
||||||
|
|||||||
@@ -172,6 +172,18 @@ impl ClientboundNotification {
|
|||||||
.ok();
|
.ok();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn publish_as_user(self, user: String) {
|
||||||
|
self.clone().publish(user.clone());
|
||||||
|
|
||||||
|
async_std::task::spawn(async move {
|
||||||
|
if let Ok(server_ids) = User::fetch_server_ids(&user).await {
|
||||||
|
for server in server_ids {
|
||||||
|
self.clone().publish(server.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<()> {
|
pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<()> {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_ids = user.fetch_server_ids().await?;
|
let server_ids = User::fetch_server_ids(&user.id).await?;
|
||||||
let mut cursor = get_collection("servers")
|
let mut cursor = get_collection("servers")
|
||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ pub async fn generate_subscriptions(user: &User) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_ids = user
|
let server_ids = User::fetch_server_ids(&user.id)
|
||||||
.fetch_server_ids()
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| "Failed to fetch memberships.".to_string())?;
|
.map_err(|_| "Failed to fetch memberships.".to_string())?;
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
}),
|
}),
|
||||||
clear: None
|
clear: None
|
||||||
}
|
}
|
||||||
.publish(id);
|
.publish_as_user(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -238,7 +238,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
}),
|
}),
|
||||||
clear: None
|
clear: None
|
||||||
}
|
}
|
||||||
.publish(id);
|
.publish_as_user(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ use rocket_contrib::json::JsonValue;
|
|||||||
|
|
||||||
#[get("/unreads")]
|
#[get("/unreads")]
|
||||||
pub async fn req(user: User) -> Result<JsonValue> {
|
pub async fn req(user: User) -> Result<JsonValue> {
|
||||||
Ok(json!(user.fetch_unreads().await?))
|
Ok(json!(User::fetch_unreads(&user.id).await?))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ pub async fn req(
|
|||||||
}),
|
}),
|
||||||
clear: None,
|
clear: None,
|
||||||
}
|
}
|
||||||
.publish(user.id.clone());
|
.publish_as_user(user.id.clone());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()>
|
|||||||
}
|
}
|
||||||
|
|
||||||
let avatar = std::mem::replace(&mut data.avatar, None);
|
let avatar = std::mem::replace(&mut data.avatar, None);
|
||||||
let attachment = if let Some(attachment_id) = avatar {
|
if let Some(attachment_id) = avatar {
|
||||||
let attachment = File::find_and_use(&attachment_id, "avatars", "user", &user.id).await?;
|
let attachment = File::find_and_use(&attachment_id, "avatars", "user", &user.id).await?;
|
||||||
set.insert(
|
set.insert(
|
||||||
"avatar",
|
"avatar",
|
||||||
@@ -110,14 +110,11 @@ pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()>
|
|||||||
);
|
);
|
||||||
|
|
||||||
remove_avatar = true;
|
remove_avatar = true;
|
||||||
Some(attachment)
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut operations = doc! {};
|
let mut operations = doc! {};
|
||||||
if set.len() > 0 {
|
if set.len() > 0 {
|
||||||
operations.insert("$set", set);
|
operations.insert("$set", &set);
|
||||||
}
|
}
|
||||||
|
|
||||||
if unset.len() > 0 {
|
if unset.len() > 0 {
|
||||||
@@ -134,32 +131,12 @@ pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()>
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(status) = &data.status {
|
ClientboundNotification::UserUpdate {
|
||||||
ClientboundNotification::UserUpdate {
|
id: user.id.clone(),
|
||||||
id: user.id.clone(),
|
data: json!(set),
|
||||||
data: json!({ "status": status }),
|
clear: data.remove,
|
||||||
clear: None,
|
|
||||||
}
|
|
||||||
.publish(user.id.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(avatar) = attachment {
|
|
||||||
ClientboundNotification::UserUpdate {
|
|
||||||
id: user.id.clone(),
|
|
||||||
data: json!({ "avatar": avatar }),
|
|
||||||
clear: None,
|
|
||||||
}
|
|
||||||
.publish(user.id.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(clear) = data.remove {
|
|
||||||
ClientboundNotification::UserUpdate {
|
|
||||||
id: user.id.clone(),
|
|
||||||
data: json!({}),
|
|
||||||
clear: Some(clear),
|
|
||||||
}
|
|
||||||
.publish(user.id.clone());
|
|
||||||
}
|
}
|
||||||
|
.publish_as_user(user.id.clone());
|
||||||
|
|
||||||
if remove_avatar {
|
if remove_avatar {
|
||||||
if let Some(old_avatar) = user.avatar {
|
if let Some(old_avatar) = user.avatar {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
pub const VERSION: &str = "0.5.1-alpha.16";
|
pub const VERSION: &str = "0.5.1-alpha.17";
|
||||||
|
|||||||
Reference in New Issue
Block a user