Improve database and RPC API for moving and linking channels, improve test legibility
This commit is contained in:
parent
439f627d9a
commit
cda54b8b5f
8 changed files with 521 additions and 531 deletions
|
@ -761,20 +761,41 @@ impl Database {
|
|||
.await
|
||||
}
|
||||
|
||||
async fn link_channel(
|
||||
// Insert an edge from the given channel to the given other channel.
|
||||
pub async fn link_channel(
|
||||
&self,
|
||||
from: ChannelId,
|
||||
user: UserId,
|
||||
channel: ChannelId,
|
||||
to: ChannelId,
|
||||
) -> Result<Vec<Channel>> {
|
||||
self.transaction(|tx| async move {
|
||||
// Note that even with these maxed permissions, this linking operation
|
||||
// is still insecure because you can't remove someone's permissions to a
|
||||
// channel if they've linked the channel to one where they're an admin.
|
||||
self.check_user_is_channel_admin(channel, user, &*tx)
|
||||
.await?;
|
||||
|
||||
self.link_channel_internal(user, channel, to, &*tx).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn link_channel_internal(
|
||||
&self,
|
||||
user: UserId,
|
||||
channel: ChannelId,
|
||||
to: ChannelId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<ChannelDescendants> {
|
||||
) -> Result<Vec<Channel>> {
|
||||
self.check_user_is_channel_admin(to, user, &*tx).await?;
|
||||
|
||||
let to_ancestors = self.get_channel_ancestors(to, &*tx).await?;
|
||||
let from_descendants = self.get_channel_descendants([from], &*tx).await?;
|
||||
let mut from_descendants = self.get_channel_descendants([channel], &*tx).await?;
|
||||
for ancestor in to_ancestors {
|
||||
if from_descendants.contains_key(&ancestor) {
|
||||
return Err(anyhow!("Cannot create a channel cycle").into());
|
||||
}
|
||||
}
|
||||
|
||||
let sql = r#"
|
||||
INSERT INTO channel_paths
|
||||
(id_path, channel_id)
|
||||
|
@ -790,14 +811,13 @@ impl Database {
|
|||
self.pool.get_database_backend(),
|
||||
sql,
|
||||
[
|
||||
from.to_proto().into(),
|
||||
from.to_proto().into(),
|
||||
channel.to_proto().into(),
|
||||
channel.to_proto().into(),
|
||||
to.to_proto().into(),
|
||||
],
|
||||
);
|
||||
tx.execute(channel_paths_stmt).await?;
|
||||
|
||||
for (from_id, to_ids) in from_descendants.iter().filter(|(id, _)| id != &&from) {
|
||||
for (from_id, to_ids) in from_descendants.iter().filter(|(id, _)| id != &&channel) {
|
||||
for to_id in to_ids {
|
||||
let channel_paths_stmt = Statement::from_sql_and_values(
|
||||
self.pool.get_database_backend(),
|
||||
|
@ -812,94 +832,116 @@ impl Database {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(from_descendants)
|
||||
if let Some(channel) = from_descendants.get_mut(&channel) {
|
||||
// Remove the other parents
|
||||
channel.clear();
|
||||
channel.insert(to);
|
||||
}
|
||||
|
||||
let channels = self.get_all_channels(from_descendants, &*tx).await?;
|
||||
|
||||
Ok(channels)
|
||||
}
|
||||
|
||||
async fn remove_channel_from_parent(
|
||||
/// Unlink a channel from a given parent. This will add in a root edge if
|
||||
/// the channel has no other parents after this operation.
|
||||
pub async fn unlink_channel(
|
||||
&self,
|
||||
from: ChannelId,
|
||||
parent: ChannelId,
|
||||
user: UserId,
|
||||
channel: ChannelId,
|
||||
from: Option<ChannelId>,
|
||||
) -> Result<()> {
|
||||
self.transaction(|tx| async move {
|
||||
// Note that even with these maxed permissions, this linking operation
|
||||
// is still insecure because you can't remove someone's permissions to a
|
||||
// channel if they've linked the channel to one where they're an admin.
|
||||
self.check_user_is_channel_admin(channel, user, &*tx)
|
||||
.await?;
|
||||
|
||||
self.unlink_channel_internal(user, channel, from, &*tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn unlink_channel_internal(
|
||||
&self,
|
||||
user: UserId,
|
||||
channel: ChannelId,
|
||||
from: Option<ChannelId>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<()> {
|
||||
let sql = r#"
|
||||
if let Some(from) = from {
|
||||
self.check_user_is_channel_admin(from, user, &*tx).await?;
|
||||
|
||||
let sql = r#"
|
||||
DELETE FROM channel_paths
|
||||
WHERE
|
||||
id_path LIKE '%' || $1 || '/' || $2 || '%'
|
||||
"#;
|
||||
let channel_paths_stmt = Statement::from_sql_and_values(
|
||||
self.pool.get_database_backend(),
|
||||
sql,
|
||||
[from.to_proto().into(), channel.to_proto().into()],
|
||||
);
|
||||
tx.execute(channel_paths_stmt).await?;
|
||||
} else {
|
||||
let sql = r#"
|
||||
DELETE FROM channel_paths
|
||||
WHERE
|
||||
id_path = '/' || $1 || '/'
|
||||
"#;
|
||||
let channel_paths_stmt = Statement::from_sql_and_values(
|
||||
self.pool.get_database_backend(),
|
||||
sql,
|
||||
[channel.to_proto().into()],
|
||||
);
|
||||
tx.execute(channel_paths_stmt).await?;
|
||||
}
|
||||
|
||||
// Make sure that there is always at least one path to the channel
|
||||
let sql = r#"
|
||||
INSERT INTO channel_paths
|
||||
(id_path, channel_id)
|
||||
SELECT
|
||||
'/' || $1 || '/', $2
|
||||
WHERE NOT EXISTS
|
||||
(SELECT *
|
||||
FROM channel_paths
|
||||
WHERE channel_id = $2)
|
||||
"#;
|
||||
|
||||
let channel_paths_stmt = Statement::from_sql_and_values(
|
||||
self.pool.get_database_backend(),
|
||||
sql,
|
||||
[parent.to_proto().into(), from.to_proto().into()],
|
||||
[channel.to_proto().into(), channel.to_proto().into()],
|
||||
);
|
||||
tx.execute(channel_paths_stmt).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move a channel from one parent to another.
|
||||
/// Note that this requires a valid parent_id in the 'from_parent' field.
|
||||
/// As channels are a DAG, we need to know which parent to remove the channel from.
|
||||
/// Here's a list of the parameters to this function and their behavior:
|
||||
///
|
||||
/// - (`None`, `None`) No op
|
||||
/// - (`None`, `Some(id)`) Link the channel without removing it from any of it's parents
|
||||
/// - (`Some(id)`, `None`) Remove a channel from a given parent, and leave other parents
|
||||
/// - (`Some(id)`, `Some(id)`) Move channel from one parent to another, leaving other parents
|
||||
///
|
||||
/// Returns the channel that was moved + it's sub channels for use
|
||||
/// by the members for `to`
|
||||
/// Move a channel from one parent to another, returns the
|
||||
/// Channels that were moved for notifying clients
|
||||
pub async fn move_channel(
|
||||
&self,
|
||||
user: UserId,
|
||||
from: ChannelId,
|
||||
from_parent: Option<ChannelId>,
|
||||
to: Option<ChannelId>,
|
||||
channel: ChannelId,
|
||||
from: Option<ChannelId>,
|
||||
to: ChannelId,
|
||||
) -> Result<Vec<Channel>> {
|
||||
self.transaction(|tx| async move {
|
||||
// Note that even with these maxed permissions, this linking operation
|
||||
// is still insecure because you can't remove someone's permissions to a
|
||||
// channel if they've linked the channel to one where they're an admin.
|
||||
self.check_user_is_channel_admin(from, user, &*tx).await?;
|
||||
self.check_user_is_channel_admin(channel, user, &*tx)
|
||||
.await?;
|
||||
|
||||
let mut channel_descendants = None;
|
||||
let moved_channels = self.link_channel_internal(user, channel, to, &*tx).await?;
|
||||
|
||||
// Note that we have to do the linking before the removal, so that we
|
||||
// can leave the channel_path table in a consistent state.
|
||||
if let Some(to) = to {
|
||||
self.check_user_is_channel_admin(to, user, &*tx).await?;
|
||||
self.unlink_channel_internal(user, channel, from, &*tx)
|
||||
.await?;
|
||||
|
||||
channel_descendants = Some(self.link_channel(from, to, &*tx).await?);
|
||||
}
|
||||
|
||||
let mut channel_descendants = match channel_descendants {
|
||||
Some(channel_descendants) => channel_descendants,
|
||||
None => self.get_channel_descendants([from], &*tx).await?,
|
||||
};
|
||||
|
||||
if let Some(from_parent) = from_parent {
|
||||
self.check_user_is_channel_admin(from_parent, user, &*tx)
|
||||
.await?;
|
||||
|
||||
self.remove_channel_from_parent(from, from_parent, &*tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let channels;
|
||||
if let Some(to) = to {
|
||||
if let Some(channel) = channel_descendants.get_mut(&from) {
|
||||
// Remove the other parents
|
||||
channel.clear();
|
||||
channel.insert(to);
|
||||
}
|
||||
|
||||
channels = self
|
||||
.get_all_channels(channel_descendants, &*tx)
|
||||
.await?;
|
||||
} else {
|
||||
channels = vec![];
|
||||
}
|
||||
|
||||
Ok(channels)
|
||||
Ok(moved_channels)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use rpc::{proto, ConnectionId};
|
||||
|
||||
use crate::{
|
||||
db::{Channel, Database, NewUserParams},
|
||||
db::{Channel, ChannelId, Database, NewUserParams},
|
||||
test_both_dbs,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
@ -501,50 +501,32 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
// ========================================================================
|
||||
// sanity check
|
||||
// Initial DAG:
|
||||
// /- gpui2
|
||||
// zed -- crdb - livestreaming - livestreaming_dag
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
assert_dag(
|
||||
result.channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: gpui2_id,
|
||||
name: "gpui2".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(crdb_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
]
|
||||
&[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(gpui2_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(crdb_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
],
|
||||
);
|
||||
|
||||
// Attempt to make a cycle
|
||||
assert!(db
|
||||
.move_channel(a_id, zed_id, None, Some(livestreaming_id))
|
||||
.link_channel(a_id, zed_id, livestreaming_id)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
// ========================================================================
|
||||
// Make a link
|
||||
db.move_channel(a_id, livestreaming_id, None, Some(zed_id))
|
||||
db.link_channel(a_id, livestreaming_id, zed_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -553,42 +535,16 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
// zed -- crdb - livestreaming - livestreaming_dag
|
||||
// \---------/
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
result.channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: gpui2_id,
|
||||
name: "gpui2".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(crdb_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(gpui2_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(crdb_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
]);
|
||||
|
||||
// ========================================================================
|
||||
// Create a new channel below a channel with multiple parents
|
||||
let livestreaming_dag_sub_id = db
|
||||
.create_channel(
|
||||
|
@ -605,50 +561,20 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
// zed -- crdb - livestreaming - livestreaming_dag - livestreaming_dag_sub_id
|
||||
// \---------/
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
result.channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: gpui2_id,
|
||||
name: "gpui2".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(crdb_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_dag_id),
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(gpui2_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(crdb_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// Make a link
|
||||
let channels = db
|
||||
.move_channel(a_id, livestreaming_dag_sub_id, None, Some(livestreaming_id))
|
||||
// ========================================================================
|
||||
// Test a complex DAG by making another link
|
||||
let returned_channels = db
|
||||
.link_channel(a_id, livestreaming_dag_sub_id, livestreaming_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -658,66 +584,32 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
// \--------/
|
||||
|
||||
// make sure we're getting just the new link
|
||||
// Not using the assert_dag helper because we want to make sure we're returning the full data
|
||||
pretty_assertions::assert_eq!(
|
||||
channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
}
|
||||
]
|
||||
returned_channels,
|
||||
vec![Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
}]
|
||||
);
|
||||
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
result.channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: gpui2_id,
|
||||
name: "gpui2".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(crdb_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_dag_id),
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(gpui2_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(crdb_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// Make another link
|
||||
let channels = db.move_channel(a_id, livestreaming_id, None, Some(gpui2_id))
|
||||
// ========================================================================
|
||||
// Test a complex DAG by making another link
|
||||
let returned_channels = db
|
||||
.link_channel(a_id, livestreaming_id, gpui2_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -727,62 +619,14 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
// \---------/
|
||||
|
||||
// Make sure that we're correctly getting the full sub-dag
|
||||
pretty_assertions::assert_eq!(channels,
|
||||
vec![Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(gpui2_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_dag_id),
|
||||
}]);
|
||||
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
result.channels,
|
||||
returned_channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: gpui2_id,
|
||||
name: "gpui2".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(gpui2_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(crdb_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
|
@ -797,12 +641,31 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_dag_id),
|
||||
},
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// Remove that inner link
|
||||
let channels = db.move_channel(a_id, livestreaming_dag_sub_id, Some(livestreaming_id), None)
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(gpui2_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(gpui2_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(crdb_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// ========================================================================
|
||||
// Test unlinking in a complex DAG by removing the inner link
|
||||
db
|
||||
.unlink_channel(
|
||||
a_id,
|
||||
livestreaming_dag_sub_id,
|
||||
Some(livestreaming_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -811,62 +674,21 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
// zed - crdb -- livestreaming - livestreaming_dag - livestreaming_dag_sub
|
||||
// \---------/
|
||||
|
||||
// Since we're not moving it to anywhere, there's nothing to notify anyone about
|
||||
pretty_assertions::assert_eq!(
|
||||
channels,
|
||||
vec![]
|
||||
);
|
||||
|
||||
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
result.channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: gpui2_id,
|
||||
name: "gpui2".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(gpui2_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(crdb_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_dag_id),
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(gpui2_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(gpui2_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(crdb_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// Remove that outer link
|
||||
db.move_channel(a_id, livestreaming_id, Some(gpui2_id), None)
|
||||
// ========================================================================
|
||||
// Test unlinking in a complex DAG by removing the inner link
|
||||
db.unlink_channel(a_id, livestreaming_id, Some(gpui2_id))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -875,49 +697,19 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
// zed - crdb -- livestreaming - livestreaming_dag - livestreaming_dag_sub
|
||||
// \---------/
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
result.channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: gpui2_id,
|
||||
name: "gpui2".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(crdb_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_dag_id),
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(gpui2_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(crdb_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// Move livestreaming to be below gpui2
|
||||
db.move_channel(a_id, livestreaming_id, Some(crdb_id), Some(gpui2_id))
|
||||
// ========================================================================
|
||||
// Test moving DAG nodes by moving livestreaming to be below gpui2
|
||||
db.move_channel(a_id, livestreaming_id, Some(crdb_id), gpui2_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -926,47 +718,17 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
// zed - crdb /
|
||||
// \---------/
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
result.channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: gpui2_id,
|
||||
name: "gpui2".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(gpui2_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_dag_id),
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(gpui2_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(gpui2_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// ========================================================================
|
||||
// Deleting a channel should not delete children that still have other parents
|
||||
db.delete_channel(gpui2_id, a_id).await.unwrap();
|
||||
|
||||
|
@ -974,46 +736,109 @@ async fn test_channels_moving(db: &Arc<Database>) {
|
|||
// zed - crdb
|
||||
// \- livestreaming - livestreaming_dag - livestreaming_dag_sub
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
pretty_assertions::assert_eq!(
|
||||
result.channels,
|
||||
vec![
|
||||
Channel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
parent_id: None,
|
||||
},
|
||||
Channel {
|
||||
id: crdb_id,
|
||||
name: "crdb".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_id,
|
||||
name: "livestreaming".to_string(),
|
||||
parent_id: Some(zed_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_id,
|
||||
name: "livestreaming_dag".to_string(),
|
||||
parent_id: Some(livestreaming_id),
|
||||
},
|
||||
Channel {
|
||||
id: livestreaming_dag_sub_id,
|
||||
name: "livestreaming_dag_sub".to_string(),
|
||||
parent_id: Some(livestreaming_dag_id),
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// But deleting a parent of a DAG should delete the whole DAG:
|
||||
db.move_channel(a_id, livestreaming_id, None, Some(crdb_id))
|
||||
// ========================================================================
|
||||
// Unlinking a channel from it's parent should automatically promote it to a root channel
|
||||
db.unlink_channel(a_id, crdb_id, Some(zed_id))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// DAG is now:
|
||||
// crdb
|
||||
// zed
|
||||
// \- livestreaming - livestreaming_dag - livestreaming_dag_sub
|
||||
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, None),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// ========================================================================
|
||||
// Unlinking a root channel should not have any effect
|
||||
db.unlink_channel(a_id, crdb_id, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// DAG is now:
|
||||
// crdb
|
||||
// zed
|
||||
// \- livestreaming - livestreaming_dag - livestreaming_dag_sub
|
||||
//
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, None),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// ========================================================================
|
||||
// You should be able to move a root channel into a non-root channel
|
||||
db.move_channel(a_id, crdb_id, None, zed_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// DAG is now:
|
||||
// zed - crdb
|
||||
// \- livestreaming - livestreaming_dag - livestreaming_dag_sub
|
||||
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Moving a non-root channel without a parent id should be the equivalent of a link operation
|
||||
db.move_channel(a_id, livestreaming_id, None, crdb_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// DAG is now:
|
||||
// zed - crdb - livestreaming - livestreaming_dag - livestreaming_dag_sub
|
||||
// \--------/
|
||||
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
assert_dag(result.channels, &[
|
||||
(zed_id, None),
|
||||
(crdb_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(zed_id)),
|
||||
(livestreaming_id, Some(crdb_id)),
|
||||
(livestreaming_dag_id, Some(livestreaming_id)),
|
||||
(livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
|
||||
]);
|
||||
|
||||
// ========================================================================
|
||||
// Deleting a parent of a DAG should delete the whole DAG:
|
||||
db.delete_channel(zed_id, a_id).await.unwrap();
|
||||
let result = db.get_channels_for_user(a_id).await.unwrap();
|
||||
assert!(result.channels.is_empty())
|
||||
assert!(
|
||||
result.channels.is_empty()
|
||||
)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_dag(actual: Vec<Channel>, expected: &[(ChannelId, Option<ChannelId>)]) {
|
||||
let actual = actual
|
||||
.iter()
|
||||
.map(|channel| (channel.id, channel.parent_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
pretty_assertions::assert_eq!(actual, expected)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ mod connection_pool;
|
|||
use crate::{
|
||||
auth,
|
||||
db::{
|
||||
self, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId, ServerId, User,
|
||||
self, Channel, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId, ServerId, User,
|
||||
UserId,
|
||||
},
|
||||
executor::Executor,
|
||||
|
@ -267,6 +267,8 @@ impl Server {
|
|||
.add_request_handler(send_channel_message)
|
||||
.add_request_handler(remove_channel_message)
|
||||
.add_request_handler(get_channel_messages)
|
||||
.add_request_handler(link_channel)
|
||||
.add_request_handler(unlink_channel)
|
||||
.add_request_handler(move_channel)
|
||||
.add_request_handler(follow)
|
||||
.add_message_handler(unfollow)
|
||||
|
@ -2391,6 +2393,72 @@ async fn rename_channel(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn link_channel(
|
||||
request: proto::LinkChannel,
|
||||
response: Response<proto::LinkChannel>,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
let db = session.db().await;
|
||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||
let to = ChannelId::from_proto(request.to);
|
||||
let channels_to_send = db.link_channel(session.user_id, channel_id, to).await?;
|
||||
|
||||
let members = db.get_channel_members(to).await?;
|
||||
let connection_pool = session.connection_pool().await;
|
||||
let update = proto::UpdateChannels {
|
||||
channels: channels_to_send
|
||||
.into_iter()
|
||||
.map(|channel| proto::Channel {
|
||||
id: channel.id.to_proto(),
|
||||
name: channel.name,
|
||||
parent_id: channel.parent_id.map(ChannelId::to_proto),
|
||||
})
|
||||
.collect(),
|
||||
..Default::default()
|
||||
};
|
||||
for member_id in members {
|
||||
for connection_id in connection_pool.user_connection_ids(member_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
response.send(Ack {})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn unlink_channel(
|
||||
request: proto::UnlinkChannel,
|
||||
response: Response<proto::UnlinkChannel>,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
let db = session.db().await;
|
||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||
let from = request.from.map(ChannelId::from_proto);
|
||||
db.unlink_channel(session.user_id, channel_id, from).await?;
|
||||
|
||||
if let Some(from_parent) = from {
|
||||
let members = db.get_channel_members(from_parent).await?;
|
||||
let update = proto::UpdateChannels {
|
||||
delete_channel_edge: vec![proto::ChannelEdge {
|
||||
channel_id: channel_id.to_proto(),
|
||||
parent_id: from_parent.to_proto(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
let connection_pool = session.connection_pool().await;
|
||||
for member_id in members {
|
||||
for connection_id in connection_pool.user_connection_ids(member_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.send(Ack {})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn move_channel(
|
||||
request: proto::MoveChannel,
|
||||
response: Response<proto::MoveChannel>,
|
||||
|
@ -2398,18 +2466,12 @@ async fn move_channel(
|
|||
) -> Result<()> {
|
||||
let db = session.db().await;
|
||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||
let from_parent = request.from_parent.map(ChannelId::from_proto);
|
||||
let to = request.to.map(ChannelId::from_proto);
|
||||
let channels_to_send = db
|
||||
.move_channel(
|
||||
session.user_id,
|
||||
channel_id,
|
||||
from_parent,
|
||||
to,
|
||||
)
|
||||
let from_parent = request.from.map(ChannelId::from_proto);
|
||||
let to = ChannelId::from_proto(request.to);
|
||||
let channels_to_send: Vec<Channel> = db
|
||||
.move_channel(session.user_id, channel_id, from_parent, to)
|
||||
.await?;
|
||||
|
||||
|
||||
if let Some(from_parent) = from_parent {
|
||||
let members = db.get_channel_members(from_parent).await?;
|
||||
let update = proto::UpdateChannels {
|
||||
|
@ -2425,24 +2487,24 @@ async fn move_channel(
|
|||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let Some(to) = to {
|
||||
let members = db.get_channel_members(to).await?;
|
||||
let connection_pool = session.connection_pool().await;
|
||||
let update = proto::UpdateChannels {
|
||||
channels: channels_to_send.into_iter().map(|channel| proto::Channel {
|
||||
let members = db.get_channel_members(to).await?;
|
||||
let connection_pool = session.connection_pool().await;
|
||||
let update = proto::UpdateChannels {
|
||||
channels: channels_to_send
|
||||
.into_iter()
|
||||
.map(|channel| proto::Channel {
|
||||
id: channel.id.to_proto(),
|
||||
name: channel.name,
|
||||
parent_id: channel.parent_id.map(ChannelId::to_proto),
|
||||
}).collect(),
|
||||
..Default::default()
|
||||
};
|
||||
for member_id in members {
|
||||
for connection_id in connection_pool.user_connection_ids(member_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
..Default::default()
|
||||
};
|
||||
for member_id in members {
|
||||
for connection_id in connection_pool.user_connection_ids(member_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -933,7 +933,7 @@ async fn test_channel_moving(deterministic: Arc<Deterministic>, cx_a: &mut TestA
|
|||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.move_channel(channel_c_id, Some(channel_b_id), Some(channel_a_id), cx)
|
||||
channel_store.move_channel(channel_c_id, Some(channel_b_id), channel_a_id, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -970,7 +970,7 @@ async fn test_channel_moving(deterministic: Arc<Deterministic>, cx_a: &mut TestA
|
|||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.move_channel(channel_c_id, None, Some(channel_b_id), cx)
|
||||
channel_store.link_channel(channel_c_id, channel_b_id, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue