From 4727f997edb31abed570820b0026362341c44c96 Mon Sep 17 00:00:00 2001 From: Paul <paulmakles@gmail.com> Date: Wed, 16 Jun 2021 23:24:31 +0100 Subject: [PATCH] New Feature: Add server-side channel unreads. --- set_version.sh | 2 +- src/database/entities/channel.rs | 14 ++++++++ src/database/entities/server.rs | 2 ++ src/database/entities/sync.rs | 16 +++++++++ src/database/entities/user.rs | 19 +++++++++++ src/database/migrations/init.rs | 8 ++--- src/notifications/events.rs | 7 +++- src/notifications/payload.rs | 2 +- src/notifications/websocket.rs | 3 +- src/routes/channels/channel_ack.rs | 53 ++++++++++++++++++++++++++++++ src/routes/channels/mod.rs | 2 ++ src/routes/sync/get_unreads.rs | 10 ++++++ src/routes/sync/mod.rs | 3 +- src/version.rs | 2 +- 14 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 src/routes/channels/channel_ack.rs create mode 100644 src/routes/sync/get_unreads.rs diff --git a/set_version.sh b/set_version.sh index 4881d51..3b31b5d 100755 --- a/set_version.sh +++ b/set_version.sh @@ -1,3 +1,3 @@ #!/bin/bash -export version=0.5.0-alpha.2 +export version=0.5.0-alpha.3 echo "pub const VERSION: &str = \"${version}\";" > src/version.rs diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs index 0ab74fc..aefa613 100644 --- a/src/database/entities/channel.rs +++ b/src/database/entities/channel.rs @@ -128,6 +128,20 @@ impl Channel { operation: "delete_many", with: "channel_invites", })?; + + // 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 diff --git a/src/database/entities/server.rs b/src/database/entities/server.rs index b30c5c4..5726d44 100644 --- a/src/database/entities/server.rs +++ b/src/database/entities/server.rs @@ -180,6 +180,8 @@ impl Server { })?; } + // ! FIXME: delete any unreads + for with in &["server_members", "server_bans"] { get_collection(with) .delete_many( diff --git a/src/database/entities/sync.rs b/src/database/entities/sync.rs index d905c69..07fb161 100644 --- a/src/database/entities/sync.rs +++ b/src/database/entities/sync.rs @@ -1,3 +1,19 @@ use std::collections::HashMap; +use serde::{Serialize, Deserialize}; pub type UserSettings = HashMap<String, (i64, String)>; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ChannelCompositeKey { + pub channel: String, + pub user: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ChannelUnread { + #[serde(rename = "_id")] + pub id: ChannelCompositeKey, + + pub last_id: String, + pub mentions: Option<Vec<String>>, +} diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs index d205ece..b94d7a5 100644 --- a/src/database/entities/user.rs +++ b/src/database/entities/user.rs @@ -253,4 +253,23 @@ impl User { .flatten() .collect::<Vec<String>>()) } + + /// Utility function to fetch unread objects for user. + pub async fn fetch_unreads(&self) -> Result<Vec<Document>> { + Ok(get_collection("channel_unreads") + .find( + doc! { + "_id.user": &self.id + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find_one", + with: "user_settings", + })? + .filter_map(async move |s| s.ok()) + .collect::<Vec<Document>>() + .await) + } } diff --git a/src/database/migrations/init.rs b/src/database/migrations/init.rs index dcb0630..1bb7277 100644 --- a/src/database/migrations/init.rs +++ b/src/database/migrations/init.rs @@ -41,6 +41,10 @@ pub async fn create_database() { .await .expect("Failed to create channel_invites collection."); + db.create_collection("channel_unreads", None) + .await + .expect("Failed to create channel_unreads collection."); + db.create_collection("migrations", None) .await .expect("Failed to create migrations collection."); @@ -49,10 +53,6 @@ pub async fn create_database() { .await .expect("Failed to create attachments collection."); - db.create_collection("channel_unreads", None) - .await - .expect("Failed to create channel_unreads collection."); - db.create_collection("user_settings", None) .await .expect("Failed to create user_settings collection."); diff --git a/src/notifications/events.rs b/src/notifications/events.rs index 6ed1aaa..6218b92 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -63,7 +63,7 @@ pub enum ClientboundNotification { Ready { users: Vec<User>, servers: Vec<Server>, - channels: Vec<Channel>, + channels: Vec<Channel> }, Message(Message), @@ -103,6 +103,11 @@ pub enum ClientboundNotification { id: String, user: String, }, + ChannelAck { + id: String, + user: String, + message_id: String + }, ServerUpdate { id: String, diff --git a/src/notifications/payload.rs b/src/notifications/payload.rs index ac5c453..6a279f0 100644 --- a/src/notifications/payload.rs +++ b/src/notifications/payload.rs @@ -113,6 +113,6 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> { Ok(ClientboundNotification::Ready { users, servers, - channels, + channels }) } diff --git a/src/notifications/websocket.rs b/src/notifications/websocket.rs index 6e275e1..5046f77 100644 --- a/src/notifications/websocket.rs +++ b/src/notifications/websocket.rs @@ -244,7 +244,8 @@ pub fn publish(ids: Vec<String>, notification: ClientboundNotification) { // Block certain notifications from reaching users that aren't meant to see them. match ¬ification { ClientboundNotification::UserRelationship { id: user_id, .. } - | ClientboundNotification::UserSettingsUpdate { id: user_id, .. } => { + | ClientboundNotification::UserSettingsUpdate { id: user_id, .. } + | ClientboundNotification::ChannelAck { user: user_id, .. } => { if &id != user_id { continue; } diff --git a/src/routes/channels/channel_ack.rs b/src/routes/channels/channel_ack.rs new file mode 100644 index 0000000..dfd45e4 --- /dev/null +++ b/src/routes/channels/channel_ack.rs @@ -0,0 +1,53 @@ +use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; +use crate::database::*; + +use mongodb::bson::doc; +use mongodb::options::UpdateOptions; + +#[put("/<target>/ack/<message>")] +pub async fn req(user: User, target: Ref, message: Ref) -> Result<()> { + let target = target.fetch_channel().await?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&target) + .for_channel() + .await?; + + if !perm.get_view() { + Err(Error::MissingPermission)? + } + + let id = target.id(); + get_collection("channel_unreads") + .update_one( + doc! { + "_id.channel": id, + "_id.user": &user.id + }, + doc! { + "$unset": { + "mentions": 1 + }, + "$set": { + "last_id": &message.id + } + }, + UpdateOptions::builder() + .upsert(true) + .build() + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "channel_unreads", + })?; + + ClientboundNotification::ChannelAck { + id: id.to_string(), + user: user.id.clone(), + message_id: message.id + }.publish(user.id); + + Ok(()) +} diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs index cb7d0b4..ad2b700 100644 --- a/src/routes/channels/mod.rs +++ b/src/routes/channels/mod.rs @@ -1,5 +1,6 @@ use rocket::Route; +mod channel_ack; mod delete_channel; mod edit_channel; mod fetch_channel; @@ -18,6 +19,7 @@ mod message_send; pub fn routes() -> Vec<Route> { routes![ + channel_ack::req, fetch_channel::req, fetch_members::req, delete_channel::req, diff --git a/src/routes/sync/get_unreads.rs b/src/routes/sync/get_unreads.rs new file mode 100644 index 0000000..4d59beb --- /dev/null +++ b/src/routes/sync/get_unreads.rs @@ -0,0 +1,10 @@ +use crate::database::*; +use crate::util::result::Result; + +use rocket_contrib::json::JsonValue; +use mongodb::bson::doc; + +#[get("/unreads")] +pub async fn req(user: User) -> Result<JsonValue> { + Ok(json!(user.fetch_unreads().await?)) +} diff --git a/src/routes/sync/mod.rs b/src/routes/sync/mod.rs index 872fef1..3bfe36d 100644 --- a/src/routes/sync/mod.rs +++ b/src/routes/sync/mod.rs @@ -2,7 +2,8 @@ use rocket::Route; mod get_settings; mod set_settings; +mod get_unreads; pub fn routes() -> Vec<Route> { - routes![get_settings::req, set_settings::req] + routes![get_settings::req, set_settings::req, get_unreads::req] } diff --git a/src/version.rs b/src/version.rs index b8bf57c..133b97d 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "0.5.0-alpha.1"; +pub const VERSION: &str = "0.5.0-alpha.3"; -- GitLab