Start work on exposing which channels the user has admin rights to

This commit is contained in:
Max Brunsfeld 2023-08-03 18:31:00 -07:00
parent 95b1ab9574
commit 7a04ee3b71
7 changed files with 70 additions and 36 deletions

View file

@ -26,6 +26,7 @@ pub struct Channel {
pub id: ChannelId, pub id: ChannelId,
pub name: String, pub name: String,
pub parent_id: Option<ChannelId>, pub parent_id: Option<ChannelId>,
pub user_is_admin: bool,
pub depth: usize, pub depth: usize,
} }
@ -247,6 +248,7 @@ impl ChannelStore {
Arc::new(Channel { Arc::new(Channel {
id: channel.id, id: channel.id,
name: channel.name, name: channel.name,
user_is_admin: false,
parent_id: None, parent_id: None,
depth: 0, depth: 0,
}), }),
@ -267,6 +269,7 @@ impl ChannelStore {
Arc::new(Channel { Arc::new(Channel {
id: channel.id, id: channel.id,
name: channel.name, name: channel.name,
user_is_admin: channel.user_is_admin,
parent_id: Some(parent_id), parent_id: Some(parent_id),
depth, depth,
}), }),
@ -278,6 +281,7 @@ impl ChannelStore {
Arc::new(Channel { Arc::new(Channel {
id: channel.id, id: channel.id,
name: channel.name, name: channel.name,
user_is_admin: channel.user_is_admin,
parent_id: None, parent_id: None,
depth: 0, depth: 0,
}), }),

View file

@ -18,11 +18,13 @@ fn test_update_channels(cx: &mut AppContext) {
id: 1, id: 1,
name: "b".to_string(), name: "b".to_string(),
parent_id: None, parent_id: None,
user_is_admin: true,
}, },
proto::Channel { proto::Channel {
id: 2, id: 2,
name: "a".to_string(), name: "a".to_string(),
parent_id: None, parent_id: None,
user_is_admin: false,
}, },
], ],
..Default::default() ..Default::default()
@ -33,8 +35,8 @@ fn test_update_channels(cx: &mut AppContext) {
&channel_store, &channel_store,
&[ &[
// //
(0, "a"), (0, "a", true),
(0, "b"), (0, "b", false),
], ],
cx, cx,
); );
@ -47,11 +49,13 @@ fn test_update_channels(cx: &mut AppContext) {
id: 3, id: 3,
name: "x".to_string(), name: "x".to_string(),
parent_id: Some(1), parent_id: Some(1),
user_is_admin: false,
}, },
proto::Channel { proto::Channel {
id: 4, id: 4,
name: "y".to_string(), name: "y".to_string(),
parent_id: Some(2), parent_id: Some(2),
user_is_admin: false,
}, },
], ],
..Default::default() ..Default::default()
@ -61,11 +65,10 @@ fn test_update_channels(cx: &mut AppContext) {
assert_channels( assert_channels(
&channel_store, &channel_store,
&[ &[
// (0, "a", true),
(0, "a"), (1, "y", true),
(1, "y"), (0, "b", false),
(0, "b"), (1, "x", false),
(1, "x"),
], ],
cx, cx,
); );
@ -81,14 +84,14 @@ fn update_channels(
fn assert_channels( fn assert_channels(
channel_store: &ModelHandle<ChannelStore>, channel_store: &ModelHandle<ChannelStore>,
expected_channels: &[(usize, &str)], expected_channels: &[(usize, &str, bool)],
cx: &AppContext, cx: &AppContext,
) { ) {
channel_store.read_with(cx, |store, _| { channel_store.read_with(cx, |store, _| {
let actual = store let actual = store
.channels() .channels()
.iter() .iter()
.map(|c| (c.depth, c.name.as_str())) .map(|c| (c.depth, c.name.as_str(), c.user_is_admin))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(actual, expected_channels); assert_eq!(actual, expected_channels);
}); });

View file

@ -3385,6 +3385,7 @@ impl Database {
.map(|channel| Channel { .map(|channel| Channel {
id: channel.id, id: channel.id,
name: channel.name, name: channel.name,
user_is_admin: false,
parent_id: None, parent_id: None,
}) })
.collect(); .collect();
@ -3401,20 +3402,21 @@ impl Database {
self.transaction(|tx| async move { self.transaction(|tx| async move {
let tx = tx; let tx = tx;
let starting_channel_ids: Vec<ChannelId> = channel_member::Entity::find() let channel_memberships = channel_member::Entity::find()
.filter( .filter(
channel_member::Column::UserId channel_member::Column::UserId
.eq(user_id) .eq(user_id)
.and(channel_member::Column::Accepted.eq(true)), .and(channel_member::Column::Accepted.eq(true)),
) )
.select_only()
.column(channel_member::Column::ChannelId)
.into_values::<_, QueryChannelIds>()
.all(&*tx) .all(&*tx)
.await?; .await?;
let admin_channel_ids = channel_memberships
.iter()
.filter_map(|m| m.admin.then_some(m.channel_id))
.collect::<HashSet<_>>();
let parents_by_child_id = self let parents_by_child_id = self
.get_channel_descendants(starting_channel_ids, &*tx) .get_channel_descendants(channel_memberships.iter().map(|m| m.channel_id), &*tx)
.await?; .await?;
let mut channels = Vec::with_capacity(parents_by_child_id.len()); let mut channels = Vec::with_capacity(parents_by_child_id.len());
@ -3428,6 +3430,7 @@ impl Database {
channels.push(Channel { channels.push(Channel {
id: row.id, id: row.id,
name: row.name, name: row.name,
user_is_admin: admin_channel_ids.contains(&row.id),
parent_id: parents_by_child_id.get(&row.id).copied().flatten(), parent_id: parents_by_child_id.get(&row.id).copied().flatten(),
}); });
} }
@ -3627,7 +3630,7 @@ impl Database {
r#" r#"
WITH RECURSIVE channel_tree(child_id, parent_id) AS ( WITH RECURSIVE channel_tree(child_id, parent_id) AS (
SELECT root_ids.column1 as child_id, CAST(NULL as INTEGER) as parent_id SELECT root_ids.column1 as child_id, CAST(NULL as INTEGER) as parent_id
FROM (VALUES {}) as root_ids FROM (VALUES {values}) as root_ids
UNION UNION
SELECT channel_parents.child_id, channel_parents.parent_id SELECT channel_parents.child_id, channel_parents.parent_id
FROM channel_parents, channel_tree FROM channel_parents, channel_tree
@ -3637,7 +3640,6 @@ impl Database {
FROM channel_tree FROM channel_tree
ORDER BY child_id, parent_id IS NOT NULL ORDER BY child_id, parent_id IS NOT NULL
"#, "#,
values
); );
#[derive(FromQueryResult, Debug, PartialEq)] #[derive(FromQueryResult, Debug, PartialEq)]
@ -3663,14 +3665,29 @@ impl Database {
Ok(parents_by_child_id) Ok(parents_by_child_id)
} }
pub async fn get_channel(&self, channel_id: ChannelId) -> Result<Option<Channel>> { pub async fn get_channel(
&self,
channel_id: ChannelId,
user_id: UserId,
) -> Result<Option<Channel>> {
self.transaction(|tx| async move { self.transaction(|tx| async move {
let tx = tx; let tx = tx;
let channel = channel::Entity::find_by_id(channel_id).one(&*tx).await?; let channel = channel::Entity::find_by_id(channel_id).one(&*tx).await?;
let user_is_admin = channel_member::Entity::find()
.filter(
channel_member::Column::ChannelId
.eq(channel_id)
.and(channel_member::Column::UserId.eq(user_id))
.and(channel_member::Column::Admin.eq(true)),
)
.count(&*tx)
.await?
> 0;
Ok(channel.map(|channel| Channel { Ok(channel.map(|channel| Channel {
id: channel.id, id: channel.id,
name: channel.name, name: channel.name,
user_is_admin,
parent_id: None, parent_id: None,
})) }))
}) })
@ -3942,6 +3959,7 @@ pub struct NewUserResult {
pub struct Channel { pub struct Channel {
pub id: ChannelId, pub id: ChannelId,
pub name: String, pub name: String,
pub user_is_admin: bool,
pub parent_id: Option<ChannelId>, pub parent_id: Option<ChannelId>,
} }
@ -4199,11 +4217,6 @@ pub struct WorktreeSettingsFile {
pub content: String, pub content: String,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryChannelIds {
ChannelId,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryUserIds { enum QueryUserIds {
UserId, UserId,

View file

@ -960,43 +960,50 @@ test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, {
id: zed_id, id: zed_id,
name: "zed".to_string(), name: "zed".to_string(),
parent_id: None, parent_id: None,
user_is_admin: true,
}, },
Channel { Channel {
id: crdb_id, id: crdb_id,
name: "crdb".to_string(), name: "crdb".to_string(),
parent_id: Some(zed_id), parent_id: Some(zed_id),
user_is_admin: true,
}, },
Channel { Channel {
id: livestreaming_id, id: livestreaming_id,
name: "livestreaming".to_string(), name: "livestreaming".to_string(),
parent_id: Some(zed_id), parent_id: Some(zed_id),
user_is_admin: true,
}, },
Channel { Channel {
id: replace_id, id: replace_id,
name: "replace".to_string(), name: "replace".to_string(),
parent_id: Some(zed_id), parent_id: Some(zed_id),
user_is_admin: true,
}, },
Channel { Channel {
id: rust_id, id: rust_id,
name: "rust".to_string(), name: "rust".to_string(),
parent_id: None, parent_id: None,
user_is_admin: true,
}, },
Channel { Channel {
id: cargo_id, id: cargo_id,
name: "cargo".to_string(), name: "cargo".to_string(),
parent_id: Some(rust_id), parent_id: Some(rust_id),
user_is_admin: true,
}, },
Channel { Channel {
id: cargo_ra_id, id: cargo_ra_id,
name: "cargo-ra".to_string(), name: "cargo-ra".to_string(),
parent_id: Some(cargo_id), parent_id: Some(cargo_id),
user_is_admin: true,
} }
] ]
); );
// Remove a single channel // Remove a single channel
db.remove_channel(crdb_id, a_id).await.unwrap(); db.remove_channel(crdb_id, a_id).await.unwrap();
assert!(db.get_channel(crdb_id).await.unwrap().is_none()); assert!(db.get_channel(crdb_id, a_id).await.unwrap().is_none());
// Remove a channel tree // Remove a channel tree
let (mut channel_ids, user_ids) = db.remove_channel(rust_id, a_id).await.unwrap(); let (mut channel_ids, user_ids) = db.remove_channel(rust_id, a_id).await.unwrap();
@ -1004,9 +1011,9 @@ test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, {
assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]); assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
assert_eq!(user_ids, &[a_id]); assert_eq!(user_ids, &[a_id]);
assert!(db.get_channel(rust_id).await.unwrap().is_none()); assert!(db.get_channel(rust_id, a_id).await.unwrap().is_none());
assert!(db.get_channel(cargo_id).await.unwrap().is_none()); assert!(db.get_channel(cargo_id, a_id).await.unwrap().is_none());
assert!(db.get_channel(cargo_ra_id).await.unwrap().is_none()); assert!(db.get_channel(cargo_ra_id, a_id).await.unwrap().is_none());
}); });
test_both_dbs!( test_both_dbs!(

View file

@ -2150,6 +2150,7 @@ async fn create_channel(
id: id.to_proto(), id: id.to_proto(),
name: request.name, name: request.name,
parent_id: request.parent_id, parent_id: request.parent_id,
user_is_admin: true,
}); });
if let Some(parent_id) = parent_id { if let Some(parent_id) = parent_id {
@ -2204,7 +2205,7 @@ async fn invite_channel_member(
let db = session.db().await; let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id); let channel_id = ChannelId::from_proto(request.channel_id);
let channel = db let channel = db
.get_channel(channel_id) .get_channel(channel_id, session.user_id)
.await? .await?
.ok_or_else(|| anyhow!("channel not found"))?; .ok_or_else(|| anyhow!("channel not found"))?;
let invitee_id = UserId::from_proto(request.user_id); let invitee_id = UserId::from_proto(request.user_id);
@ -2216,6 +2217,7 @@ async fn invite_channel_member(
id: channel.id.to_proto(), id: channel.id.to_proto(),
name: channel.name, name: channel.name,
parent_id: None, parent_id: None,
user_is_admin: false,
}); });
for connection_id in session for connection_id in session
.connection_pool() .connection_pool()
@ -2264,12 +2266,12 @@ async fn respond_to_channel_invite(
) -> Result<()> { ) -> Result<()> {
let db = session.db().await; let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id); let channel_id = ChannelId::from_proto(request.channel_id);
let channel = db
.get_channel(channel_id)
.await?
.ok_or_else(|| anyhow!("no such channel"))?;
db.respond_to_channel_invite(channel_id, session.user_id, request.accept) db.respond_to_channel_invite(channel_id, session.user_id, request.accept)
.await?; .await?;
let channel = db
.get_channel(channel_id, session.user_id)
.await?
.ok_or_else(|| anyhow!("no such channel"))?;
let mut update = proto::UpdateChannels::default(); let mut update = proto::UpdateChannels::default();
update update
@ -2279,6 +2281,7 @@ async fn respond_to_channel_invite(
update.channels.push(proto::Channel { update.channels.push(proto::Channel {
id: channel.id.to_proto(), id: channel.id.to_proto(),
name: channel.name, name: channel.name,
user_is_admin: channel.user_is_admin,
parent_id: None, parent_id: None,
}); });
} }
@ -2430,6 +2433,7 @@ fn build_initial_channels_update(
update.channels.push(proto::Channel { update.channels.push(proto::Channel {
id: channel.id.to_proto(), id: channel.id.to_proto(),
name: channel.name, name: channel.name,
user_is_admin: channel.user_is_admin,
parent_id: channel.parent_id.map(|id| id.to_proto()), parent_id: channel.parent_id.map(|id| id.to_proto()),
}); });
} }
@ -2447,6 +2451,7 @@ fn build_initial_channels_update(
update.channel_invitations.push(proto::Channel { update.channel_invitations.push(proto::Channel {
id: channel.id.to_proto(), id: channel.id.to_proto(),
name: channel.name, name: channel.name,
user_is_admin: false,
parent_id: None, parent_id: None,
}); });
} }

View file

@ -1,13 +1,10 @@
use crate::tests::{room_participants, RoomParticipants, TestServer};
use call::ActiveCall; use call::ActiveCall;
use client::{Channel, User}; use client::{Channel, User};
use gpui::{executor::Deterministic, TestAppContext}; use gpui::{executor::Deterministic, TestAppContext};
use rpc::proto; use rpc::proto;
use std::sync::Arc; use std::sync::Arc;
use crate::tests::{room_participants, RoomParticipants};
use super::TestServer;
#[gpui::test] #[gpui::test]
async fn test_basic_channels( async fn test_basic_channels(
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,
@ -35,6 +32,7 @@ async fn test_basic_channels(
id: channel_a_id, id: channel_a_id,
name: "channel-a".to_string(), name: "channel-a".to_string(),
parent_id: None, parent_id: None,
user_is_admin: true,
depth: 0, depth: 0,
})] })]
) )
@ -69,6 +67,7 @@ async fn test_basic_channels(
id: channel_a_id, id: channel_a_id,
name: "channel-a".to_string(), name: "channel-a".to_string(),
parent_id: None, parent_id: None,
user_is_admin: false,
depth: 0, depth: 0,
})] })]
) )
@ -111,6 +110,7 @@ async fn test_basic_channels(
id: channel_a_id, id: channel_a_id,
name: "channel-a".to_string(), name: "channel-a".to_string(),
parent_id: None, parent_id: None,
user_is_admin: false,
depth: 0, depth: 0,
})] })]
) )
@ -204,6 +204,7 @@ async fn test_channel_room(
id: zed_id, id: zed_id,
name: "zed".to_string(), name: "zed".to_string(),
parent_id: None, parent_id: None,
user_is_admin: false,
depth: 0, depth: 0,
})] })]
) )

View file

@ -1295,7 +1295,8 @@ message Nonce {
message Channel { message Channel {
uint64 id = 1; uint64 id = 1;
string name = 2; string name = 2;
optional uint64 parent_id = 3; bool user_is_admin = 3;
optional uint64 parent_id = 4;
} }
message Contact { message Contact {