Simplify Membership Management (#6747)
Simplify Zed's collaboration system by: - Only allowing member management on root channels. - Disallowing moving sub-channels between different roots. - Disallowing public channels nested under private channels. This should make the mental model easier to understand, and makes it clearer who has what access. It is also significantly simpler to implement, and so hopefully more performant and less buggy. Still TODO: - [x] Update collab_ui to match. - [x] Fix channel buffer tests. Release Notes: - Simplified channel membership management.
This commit is contained in:
commit
8bc105ca1d
31 changed files with 900 additions and 1441 deletions
|
@ -40,7 +40,7 @@ use std::{
|
|||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use tables::*;
|
||||
pub use tables::*;
|
||||
use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
|
||||
pub use ids::*;
|
||||
|
@ -502,35 +502,6 @@ pub struct NewUserResult {
|
|||
pub signup_device_id: Option<String>,
|
||||
}
|
||||
|
||||
/// The result of moving a channel.
|
||||
#[derive(Debug)]
|
||||
pub struct MoveChannelResult {
|
||||
pub previous_participants: Vec<ChannelMember>,
|
||||
pub descendent_ids: Vec<ChannelId>,
|
||||
}
|
||||
|
||||
/// The result of renaming a channel.
|
||||
#[derive(Debug)]
|
||||
pub struct RenameChannelResult {
|
||||
pub channel: Channel,
|
||||
pub participants_to_update: HashMap<UserId, Channel>,
|
||||
}
|
||||
|
||||
/// The result of creating a channel.
|
||||
#[derive(Debug)]
|
||||
pub struct CreateChannelResult {
|
||||
pub channel: Channel,
|
||||
pub participants_to_update: Vec<(UserId, ChannelsForUser)>,
|
||||
}
|
||||
|
||||
/// The result of setting a channel's visibility.
|
||||
#[derive(Debug)]
|
||||
pub struct SetChannelVisibilityResult {
|
||||
pub participants_to_update: HashMap<UserId, ChannelsForUser>,
|
||||
pub participants_to_remove: HashSet<UserId>,
|
||||
pub channels_to_remove: Vec<ChannelId>,
|
||||
}
|
||||
|
||||
/// The result of updating a channel membership.
|
||||
#[derive(Debug)]
|
||||
pub struct MembershipUpdated {
|
||||
|
@ -570,18 +541,16 @@ pub struct Channel {
|
|||
pub id: ChannelId,
|
||||
pub name: String,
|
||||
pub visibility: ChannelVisibility,
|
||||
pub role: ChannelRole,
|
||||
/// parent_path is the channel ids from the root to this one (not including this one)
|
||||
pub parent_path: Vec<ChannelId>,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
fn from_model(value: channel::Model, role: ChannelRole) -> Self {
|
||||
fn from_model(value: channel::Model) -> Self {
|
||||
Channel {
|
||||
id: value.id,
|
||||
visibility: value.visibility,
|
||||
name: value.clone().name,
|
||||
role,
|
||||
parent_path: value.ancestors().collect(),
|
||||
}
|
||||
}
|
||||
|
@ -591,7 +560,6 @@ impl Channel {
|
|||
id: self.id.to_proto(),
|
||||
name: self.name.clone(),
|
||||
visibility: self.visibility.into(),
|
||||
role: self.role.into(),
|
||||
parent_path: self.parent_path.iter().map(|c| c.to_proto()).collect(),
|
||||
}
|
||||
}
|
||||
|
@ -617,9 +585,10 @@ impl ChannelMember {
|
|||
#[derive(Debug, PartialEq)]
|
||||
pub struct ChannelsForUser {
|
||||
pub channels: Vec<Channel>,
|
||||
pub channel_memberships: Vec<channel_member::Model>,
|
||||
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
||||
pub unseen_buffer_changes: Vec<proto::UnseenChannelBufferChange>,
|
||||
pub channel_messages: Vec<proto::UnseenChannelMessage>,
|
||||
pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||
pub latest_channel_messages: Vec<proto::ChannelMessageId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -129,6 +129,15 @@ impl ChannelRole {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn can_see_channel(&self, visibility: ChannelVisibility) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Guest => visibility == ChannelVisibility::Public,
|
||||
Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// True if the role allows access to all descendant channels
|
||||
pub fn can_see_all_descendants(&self) -> bool {
|
||||
use ChannelRole::*;
|
||||
|
|
|
@ -748,18 +748,11 @@ impl Database {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn unseen_channel_buffer_changes(
|
||||
pub async fn latest_channel_buffer_changes(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
channel_ids: &[ChannelId],
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::UnseenChannelBufferChange>> {
|
||||
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
||||
enum QueryIds {
|
||||
ChannelId,
|
||||
Id,
|
||||
}
|
||||
|
||||
) -> Result<Vec<proto::ChannelBufferVersion>> {
|
||||
let mut channel_ids_by_buffer_id = HashMap::default();
|
||||
let mut rows = buffer::Entity::find()
|
||||
.filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
||||
|
@ -771,51 +764,23 @@ impl Database {
|
|||
}
|
||||
drop(rows);
|
||||
|
||||
let mut observed_edits_by_buffer_id = HashMap::default();
|
||||
let mut rows = observed_buffer_edits::Entity::find()
|
||||
.filter(observed_buffer_edits::Column::UserId.eq(user_id))
|
||||
.filter(
|
||||
observed_buffer_edits::Column::BufferId
|
||||
.is_in(channel_ids_by_buffer_id.keys().copied()),
|
||||
)
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
observed_edits_by_buffer_id.insert(row.buffer_id, row);
|
||||
}
|
||||
drop(rows);
|
||||
|
||||
let latest_operations = self
|
||||
.get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), &*tx)
|
||||
.await?;
|
||||
|
||||
let mut changes = Vec::default();
|
||||
for latest in latest_operations {
|
||||
if let Some(observed) = observed_edits_by_buffer_id.get(&latest.buffer_id) {
|
||||
if (
|
||||
observed.epoch,
|
||||
observed.lamport_timestamp,
|
||||
observed.replica_id,
|
||||
) >= (latest.epoch, latest.lamport_timestamp, latest.replica_id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(channel_id) = channel_ids_by_buffer_id.get(&latest.buffer_id) {
|
||||
changes.push(proto::UnseenChannelBufferChange {
|
||||
channel_id: channel_id.to_proto(),
|
||||
epoch: latest.epoch as u64,
|
||||
Ok(latest_operations
|
||||
.iter()
|
||||
.flat_map(|op| {
|
||||
Some(proto::ChannelBufferVersion {
|
||||
channel_id: channel_ids_by_buffer_id.get(&op.buffer_id)?.to_proto(),
|
||||
epoch: op.epoch as u64,
|
||||
version: vec![proto::VectorClockEntry {
|
||||
replica_id: latest.replica_id as u32,
|
||||
timestamp: latest.lamport_timestamp as u32,
|
||||
replica_id: op.replica_id as u32,
|
||||
timestamp: op.lamport_timestamp as u32,
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(changes)
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Returns the latest operations for the buffers with the specified IDs.
|
||||
|
|
|
@ -19,7 +19,7 @@ impl Database {
|
|||
|
||||
#[cfg(test)]
|
||||
pub async fn create_root_channel(&self, name: &str, creator_id: UserId) -> Result<ChannelId> {
|
||||
Ok(self.create_channel(name, None, creator_id).await?.id)
|
||||
Ok(self.create_channel(name, None, creator_id).await?.0.id)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -32,6 +32,7 @@ impl Database {
|
|||
Ok(self
|
||||
.create_channel(name, Some(parent), creator_id)
|
||||
.await?
|
||||
.0
|
||||
.id)
|
||||
}
|
||||
|
||||
|
@ -41,10 +42,15 @@ impl Database {
|
|||
name: &str,
|
||||
parent_channel_id: Option<ChannelId>,
|
||||
admin_id: UserId,
|
||||
) -> Result<Channel> {
|
||||
) -> Result<(
|
||||
Channel,
|
||||
Option<channel_member::Model>,
|
||||
Vec<channel_member::Model>,
|
||||
)> {
|
||||
let name = Self::sanitize_channel_name(name)?;
|
||||
self.transaction(move |tx| async move {
|
||||
let mut parent = None;
|
||||
let mut membership = None;
|
||||
|
||||
if let Some(parent_channel_id) = parent_channel_id {
|
||||
let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?;
|
||||
|
@ -68,18 +74,25 @@ impl Database {
|
|||
.await?;
|
||||
|
||||
if parent.is_none() {
|
||||
channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel.id),
|
||||
user_id: ActiveValue::Set(admin_id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Admin),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
membership = Some(
|
||||
channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel.id),
|
||||
user_id: ActiveValue::Set(admin_id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Admin),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Channel::from_model(channel, ChannelRole::Admin))
|
||||
let channel_members = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok((Channel::from_model(channel), membership, channel_members))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
@ -122,16 +135,9 @@ impl Database {
|
|||
);
|
||||
} else if channel.visibility == ChannelVisibility::Public {
|
||||
role = Some(ChannelRole::Guest);
|
||||
let channel_to_join = self
|
||||
.public_ancestors_including_self(&channel, &*tx)
|
||||
.await?
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or(channel.clone());
|
||||
|
||||
channel_member::Entity::insert(channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel_to_join.id),
|
||||
channel_id: ActiveValue::Set(channel.root_id()),
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Guest),
|
||||
|
@ -140,7 +146,7 @@ impl Database {
|
|||
.await?;
|
||||
|
||||
accept_invite_result = Some(
|
||||
self.calculate_membership_updated(&channel_to_join, user_id, &*tx)
|
||||
self.calculate_membership_updated(&channel, user_id, &*tx)
|
||||
.await?,
|
||||
);
|
||||
|
||||
|
@ -173,76 +179,47 @@ impl Database {
|
|||
channel_id: ChannelId,
|
||||
visibility: ChannelVisibility,
|
||||
admin_id: UserId,
|
||||
) -> Result<SetChannelVisibilityResult> {
|
||||
) -> Result<(Channel, Vec<channel_member::Model>)> {
|
||||
self.transaction(move |tx| async move {
|
||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||
|
||||
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
||||
.await?;
|
||||
|
||||
let previous_members = self
|
||||
.get_channel_participant_details_internal(&channel, &*tx)
|
||||
.await?;
|
||||
if visibility == ChannelVisibility::Public {
|
||||
if let Some(parent_id) = channel.parent_id() {
|
||||
let parent = self.get_channel_internal(parent_id, &*tx).await?;
|
||||
|
||||
if parent.visibility != ChannelVisibility::Public {
|
||||
Err(ErrorCode::BadPublicNesting
|
||||
.with_tag("direction", "parent")
|
||||
.anyhow())?;
|
||||
}
|
||||
}
|
||||
} else if visibility == ChannelVisibility::Members {
|
||||
if self
|
||||
.get_channel_descendants_including_self(vec![channel_id], &*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.any(|channel| {
|
||||
channel.id != channel_id && channel.visibility == ChannelVisibility::Public
|
||||
})
|
||||
{
|
||||
Err(ErrorCode::BadPublicNesting
|
||||
.with_tag("direction", "children")
|
||||
.anyhow())?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut model = channel.into_active_model();
|
||||
model.visibility = ActiveValue::Set(visibility);
|
||||
let channel = model.update(&*tx).await?;
|
||||
|
||||
let mut participants_to_update: HashMap<UserId, ChannelsForUser> = self
|
||||
.participants_to_notify_for_channel_change(&channel, &*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect();
|
||||
let channel_members = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let mut channels_to_remove: Vec<ChannelId> = vec![];
|
||||
let mut participants_to_remove: HashSet<UserId> = HashSet::default();
|
||||
match visibility {
|
||||
ChannelVisibility::Members => {
|
||||
let all_descendents: Vec<ChannelId> = self
|
||||
.get_channel_descendants_including_self(vec![channel_id], &*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|channel| channel.id)
|
||||
.collect();
|
||||
|
||||
channels_to_remove = channel::Entity::find()
|
||||
.filter(
|
||||
channel::Column::Id
|
||||
.is_in(all_descendents)
|
||||
.and(channel::Column::Visibility.eq(ChannelVisibility::Public)),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|channel| channel.id)
|
||||
.collect();
|
||||
|
||||
channels_to_remove.push(channel_id);
|
||||
|
||||
for member in previous_members {
|
||||
if member.role.can_only_see_public_descendants() {
|
||||
participants_to_remove.insert(member.user_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
ChannelVisibility::Public => {
|
||||
if let Some(public_parent) = self.public_parent_channel(&channel, &*tx).await? {
|
||||
let parent_updates = self
|
||||
.participants_to_notify_for_channel_change(&public_parent, &*tx)
|
||||
.await?;
|
||||
|
||||
for (user_id, channels) in parent_updates {
|
||||
participants_to_update.insert(user_id, channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SetChannelVisibilityResult {
|
||||
participants_to_update,
|
||||
participants_to_remove,
|
||||
channels_to_remove,
|
||||
})
|
||||
Ok((Channel::from_model(channel), channel_members))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
@ -275,7 +252,7 @@ impl Database {
|
|||
.await?;
|
||||
|
||||
let members_to_notify: Vec<UserId> = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
|
||||
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||
.select_only()
|
||||
.column(channel_member::Column::UserId)
|
||||
.distinct()
|
||||
|
@ -312,6 +289,9 @@ impl Database {
|
|||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||
self.check_user_is_channel_admin(&channel, inviter_id, &*tx)
|
||||
.await?;
|
||||
if !channel.is_root() {
|
||||
Err(ErrorCode::NotARootChannel.anyhow())?
|
||||
}
|
||||
|
||||
channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
|
@ -323,7 +303,7 @@ impl Database {
|
|||
.insert(&*tx)
|
||||
.await?;
|
||||
|
||||
let channel = Channel::from_model(channel, role);
|
||||
let channel = Channel::from_model(channel);
|
||||
|
||||
let notifications = self
|
||||
.create_notification(
|
||||
|
@ -362,35 +342,24 @@ impl Database {
|
|||
channel_id: ChannelId,
|
||||
admin_id: UserId,
|
||||
new_name: &str,
|
||||
) -> Result<RenameChannelResult> {
|
||||
) -> Result<(Channel, Vec<channel_member::Model>)> {
|
||||
self.transaction(move |tx| async move {
|
||||
let new_name = Self::sanitize_channel_name(new_name)?.to_string();
|
||||
|
||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||
let role = self
|
||||
.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
||||
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
||||
.await?;
|
||||
|
||||
let mut model = channel.into_active_model();
|
||||
model.name = ActiveValue::Set(new_name.clone());
|
||||
let channel = model.update(&*tx).await?;
|
||||
|
||||
let participants = self
|
||||
.get_channel_participant_details_internal(&channel, &*tx)
|
||||
let channel_members = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(RenameChannelResult {
|
||||
channel: Channel::from_model(channel.clone(), role),
|
||||
participants_to_update: participants
|
||||
.iter()
|
||||
.map(|participant| {
|
||||
(
|
||||
participant.user_id,
|
||||
Channel::from_model(channel.clone(), participant.role),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
Ok((Channel::from_model(channel), channel_members))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
@ -565,10 +534,7 @@ impl Database {
|
|||
|
||||
let channels = channels
|
||||
.into_iter()
|
||||
.filter_map(|channel| {
|
||||
let role = *role_for_channel.get(&channel.id)?;
|
||||
Some(Channel::from_model(channel, role))
|
||||
})
|
||||
.filter_map(|channel| Some(Channel::from_model(channel)))
|
||||
.collect();
|
||||
|
||||
Ok(channels)
|
||||
|
@ -576,6 +542,26 @@ impl Database {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn get_channel_memberships(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> Result<(Vec<channel_member::Model>, Vec<channel::Model>)> {
|
||||
self.transaction(|tx| async move {
|
||||
let memberships = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::UserId.eq(user_id))
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
let channels = self
|
||||
.get_channel_descendants_including_self(
|
||||
memberships.iter().map(|m| m.channel_id),
|
||||
&*tx,
|
||||
)
|
||||
.await?;
|
||||
Ok((memberships, channels))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns all channels for the user with the given ID.
|
||||
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
|
||||
self.transaction(|tx| async move {
|
||||
|
@ -594,12 +580,16 @@ impl Database {
|
|||
ancestor_channel: Option<&channel::Model>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<ChannelsForUser> {
|
||||
let mut filter = channel_member::Column::UserId
|
||||
.eq(user_id)
|
||||
.and(channel_member::Column::Accepted.eq(true));
|
||||
|
||||
if let Some(ancestor) = ancestor_channel {
|
||||
filter = filter.and(channel_member::Column::ChannelId.eq(ancestor.root_id()));
|
||||
}
|
||||
|
||||
let channel_memberships = channel_member::Entity::find()
|
||||
.filter(
|
||||
channel_member::Column::UserId
|
||||
.eq(user_id)
|
||||
.and(channel_member::Column::Accepted.eq(true)),
|
||||
)
|
||||
.filter(filter)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
|
@ -610,56 +600,20 @@ impl Database {
|
|||
)
|
||||
.await?;
|
||||
|
||||
let mut roles_by_channel_id: HashMap<ChannelId, ChannelRole> = HashMap::default();
|
||||
for membership in channel_memberships.iter() {
|
||||
roles_by_channel_id.insert(membership.channel_id, membership.role);
|
||||
}
|
||||
|
||||
let mut visible_channel_ids: HashSet<ChannelId> = HashSet::default();
|
||||
let roles_by_channel_id = channel_memberships
|
||||
.iter()
|
||||
.map(|membership| (membership.channel_id, membership.role))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let channels: Vec<Channel> = descendants
|
||||
.into_iter()
|
||||
.filter_map(|channel| {
|
||||
let parent_role = channel
|
||||
.parent_id()
|
||||
.and_then(|parent_id| roles_by_channel_id.get(&parent_id));
|
||||
|
||||
let role = if let Some(parent_role) = parent_role {
|
||||
let role = if let Some(existing_role) = roles_by_channel_id.get(&channel.id) {
|
||||
existing_role.max(*parent_role)
|
||||
} else {
|
||||
*parent_role
|
||||
};
|
||||
roles_by_channel_id.insert(channel.id, role);
|
||||
role
|
||||
let parent_role = roles_by_channel_id.get(&channel.root_id())?;
|
||||
if parent_role.can_see_channel(channel.visibility) {
|
||||
Some(Channel::from_model(channel))
|
||||
} else {
|
||||
*roles_by_channel_id.get(&channel.id)?
|
||||
};
|
||||
|
||||
let can_see_parent_paths = role.can_see_all_descendants()
|
||||
|| role.can_only_see_public_descendants()
|
||||
&& channel.visibility == ChannelVisibility::Public;
|
||||
if !can_see_parent_paths {
|
||||
return None;
|
||||
None
|
||||
}
|
||||
|
||||
visible_channel_ids.insert(channel.id);
|
||||
|
||||
if let Some(ancestor) = ancestor_channel {
|
||||
if !channel
|
||||
.ancestors_including_self()
|
||||
.any(|id| id == ancestor.id)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut channel = Channel::from_model(channel, role);
|
||||
channel
|
||||
.parent_path
|
||||
.retain(|id| visible_channel_ids.contains(&id));
|
||||
|
||||
Some(channel)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -687,89 +641,21 @@ impl Database {
|
|||
}
|
||||
|
||||
let channel_ids = channels.iter().map(|c| c.id).collect::<Vec<_>>();
|
||||
let channel_buffer_changes = self
|
||||
.unseen_channel_buffer_changes(user_id, &channel_ids, &*tx)
|
||||
let latest_buffer_versions = self
|
||||
.latest_channel_buffer_changes(&channel_ids, &*tx)
|
||||
.await?;
|
||||
|
||||
let unseen_messages = self
|
||||
.unseen_channel_messages(user_id, &channel_ids, &*tx)
|
||||
.await?;
|
||||
let latest_messages = self.latest_channel_messages(&channel_ids, &*tx).await?;
|
||||
|
||||
Ok(ChannelsForUser {
|
||||
channel_memberships,
|
||||
channels,
|
||||
channel_participants,
|
||||
unseen_buffer_changes: channel_buffer_changes,
|
||||
channel_messages: unseen_messages,
|
||||
latest_buffer_versions,
|
||||
latest_channel_messages: latest_messages,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn new_participants_to_notify(
|
||||
&self,
|
||||
parent_channel_id: ChannelId,
|
||||
) -> Result<Vec<(UserId, ChannelsForUser)>> {
|
||||
self.weak_transaction(|tx| async move {
|
||||
let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?;
|
||||
self.participants_to_notify_for_channel_change(&parent_channel, &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// TODO: this is very expensive, and we should rethink
|
||||
async fn participants_to_notify_for_channel_change(
|
||||
&self,
|
||||
new_parent: &channel::Model,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<(UserId, ChannelsForUser)>> {
|
||||
let mut results: Vec<(UserId, ChannelsForUser)> = Vec::new();
|
||||
|
||||
let members = self
|
||||
.get_channel_participant_details_internal(new_parent, &*tx)
|
||||
.await?;
|
||||
|
||||
for member in members.iter() {
|
||||
if !member.role.can_see_all_descendants() {
|
||||
continue;
|
||||
}
|
||||
results.push((
|
||||
member.user_id,
|
||||
self.get_user_channels(member.user_id, Some(new_parent), &*tx)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
let public_parents = self
|
||||
.public_ancestors_including_self(new_parent, &*tx)
|
||||
.await?;
|
||||
let public_parent = public_parents.last();
|
||||
|
||||
let Some(public_parent) = public_parent else {
|
||||
return Ok(results);
|
||||
};
|
||||
|
||||
// could save some time in the common case by skipping this if the
|
||||
// new channel is not public and has no public descendants.
|
||||
let public_members = if public_parent == new_parent {
|
||||
members
|
||||
} else {
|
||||
self.get_channel_participant_details_internal(public_parent, &*tx)
|
||||
.await?
|
||||
};
|
||||
|
||||
for member in public_members {
|
||||
if !member.role.can_only_see_public_descendants() {
|
||||
continue;
|
||||
};
|
||||
results.push((
|
||||
member.user_id,
|
||||
self.get_user_channels(member.user_id, Some(public_parent), &*tx)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Sets the role for the specified channel member.
|
||||
pub async fn set_channel_member_role(
|
||||
&self,
|
||||
|
@ -807,7 +693,7 @@ impl Database {
|
|||
))
|
||||
} else {
|
||||
Ok(SetMemberRoleResult::InviteUpdated(Channel::from_model(
|
||||
channel, role,
|
||||
channel,
|
||||
)))
|
||||
}
|
||||
})
|
||||
|
@ -837,22 +723,30 @@ impl Database {
|
|||
if role == ChannelRole::Admin {
|
||||
Ok(members
|
||||
.into_iter()
|
||||
.map(|channel_member| channel_member.to_proto())
|
||||
.map(|channel_member| proto::ChannelMember {
|
||||
role: channel_member.role.into(),
|
||||
user_id: channel_member.user_id.to_proto(),
|
||||
kind: if channel_member.accepted {
|
||||
Kind::Member
|
||||
} else {
|
||||
Kind::Invitee
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
.collect())
|
||||
} else {
|
||||
return Ok(members
|
||||
.into_iter()
|
||||
.filter_map(|member| {
|
||||
if member.kind == proto::channel_member::Kind::Invitee {
|
||||
if !member.accepted {
|
||||
return None;
|
||||
}
|
||||
Some(ChannelMember {
|
||||
role: member.role,
|
||||
user_id: member.user_id,
|
||||
kind: proto::channel_member::Kind::Member,
|
||||
Some(proto::ChannelMember {
|
||||
role: member.role.into(),
|
||||
user_id: member.user_id.to_proto(),
|
||||
kind: Kind::Member.into(),
|
||||
})
|
||||
})
|
||||
.map(|channel_member| channel_member.to_proto())
|
||||
.collect());
|
||||
}
|
||||
}
|
||||
|
@ -861,83 +755,11 @@ impl Database {
|
|||
&self,
|
||||
channel: &channel::Model,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<ChannelMember>> {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryMemberDetails {
|
||||
UserId,
|
||||
Role,
|
||||
IsDirectMember,
|
||||
Accepted,
|
||||
Visibility,
|
||||
}
|
||||
|
||||
let mut stream = channel_member::Entity::find()
|
||||
.left_join(channel::Entity)
|
||||
.filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
|
||||
.select_only()
|
||||
.column(channel_member::Column::UserId)
|
||||
.column(channel_member::Column::Role)
|
||||
.column_as(
|
||||
channel_member::Column::ChannelId.eq(channel.id),
|
||||
QueryMemberDetails::IsDirectMember,
|
||||
)
|
||||
.column(channel_member::Column::Accepted)
|
||||
.column(channel::Column::Visibility)
|
||||
.into_values::<_, QueryMemberDetails>()
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
|
||||
let mut user_details: HashMap<UserId, ChannelMember> = HashMap::default();
|
||||
|
||||
while let Some(user_membership) = stream.next().await {
|
||||
let (user_id, channel_role, is_direct_member, is_invite_accepted, visibility): (
|
||||
UserId,
|
||||
ChannelRole,
|
||||
bool,
|
||||
bool,
|
||||
ChannelVisibility,
|
||||
) = user_membership?;
|
||||
let kind = match (is_direct_member, is_invite_accepted) {
|
||||
(true, true) => proto::channel_member::Kind::Member,
|
||||
(true, false) => proto::channel_member::Kind::Invitee,
|
||||
(false, true) => proto::channel_member::Kind::AncestorMember,
|
||||
(false, false) => continue,
|
||||
};
|
||||
|
||||
if channel_role == ChannelRole::Guest
|
||||
&& visibility != ChannelVisibility::Public
|
||||
&& channel.visibility != ChannelVisibility::Public
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(details_mut) = user_details.get_mut(&user_id) {
|
||||
if channel_role.should_override(details_mut.role) {
|
||||
details_mut.role = channel_role;
|
||||
}
|
||||
if kind == Kind::Member {
|
||||
details_mut.kind = kind;
|
||||
// the UI is going to be a bit confusing if you already have permissions
|
||||
// that are greater than or equal to the ones you're being invited to.
|
||||
} else if kind == Kind::Invitee && details_mut.kind == Kind::AncestorMember {
|
||||
details_mut.kind = kind;
|
||||
}
|
||||
} else {
|
||||
user_details.insert(
|
||||
user_id,
|
||||
ChannelMember {
|
||||
user_id,
|
||||
kind,
|
||||
role: channel_role,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(user_details
|
||||
.into_iter()
|
||||
.map(|(_, details)| details)
|
||||
.collect())
|
||||
) -> Result<Vec<channel_member::Model>> {
|
||||
Ok(channel_member::Entity::find()
|
||||
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||
.all(tx)
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Returns the participants in the given channel.
|
||||
|
@ -1016,7 +838,7 @@ impl Database {
|
|||
tx: &DatabaseTransaction,
|
||||
) -> Result<Option<channel_member::Model>> {
|
||||
let row = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::ChannelId.is_in(channel.ancestors_including_self()))
|
||||
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
|
||||
.filter(channel_member::Column::UserId.eq(user_id))
|
||||
.filter(channel_member::Column::Accepted.eq(false))
|
||||
.one(&*tx)
|
||||
|
@ -1025,33 +847,6 @@ impl Database {
|
|||
Ok(row)
|
||||
}
|
||||
|
||||
async fn public_parent_channel(
|
||||
&self,
|
||||
channel: &channel::Model,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Option<channel::Model>> {
|
||||
let mut path = self.public_ancestors_including_self(channel, &*tx).await?;
|
||||
if path.last().unwrap().id == channel.id {
|
||||
path.pop();
|
||||
}
|
||||
Ok(path.pop())
|
||||
}
|
||||
|
||||
pub(crate) async fn public_ancestors_including_self(
|
||||
&self,
|
||||
channel: &channel::Model,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<channel::Model>> {
|
||||
let visible_channels = channel::Entity::find()
|
||||
.filter(channel::Column::Id.is_in(channel.ancestors_including_self()))
|
||||
.filter(channel::Column::Visibility.eq(ChannelVisibility::Public))
|
||||
.order_by_asc(channel::Column::ParentPath)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(visible_channels)
|
||||
}
|
||||
|
||||
/// Returns the role for a user in the given channel.
|
||||
pub async fn channel_role_for_user(
|
||||
&self,
|
||||
|
@ -1059,77 +854,25 @@ impl Database {
|
|||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Option<ChannelRole>> {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryChannelMembership {
|
||||
ChannelId,
|
||||
Role,
|
||||
Visibility,
|
||||
}
|
||||
|
||||
let mut rows = channel_member::Entity::find()
|
||||
.left_join(channel::Entity)
|
||||
let membership = channel_member::Entity::find()
|
||||
.filter(
|
||||
channel_member::Column::ChannelId
|
||||
.is_in(channel.ancestors_including_self())
|
||||
.eq(channel.root_id())
|
||||
.and(channel_member::Column::UserId.eq(user_id))
|
||||
.and(channel_member::Column::Accepted.eq(true)),
|
||||
)
|
||||
.select_only()
|
||||
.column(channel_member::Column::ChannelId)
|
||||
.column(channel_member::Column::Role)
|
||||
.column(channel::Column::Visibility)
|
||||
.into_values::<_, QueryChannelMembership>()
|
||||
.stream(&*tx)
|
||||
.one(&*tx)
|
||||
.await?;
|
||||
|
||||
let mut user_role: Option<ChannelRole> = None;
|
||||
let Some(membership) = membership else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut is_participant = false;
|
||||
let mut current_channel_visibility = None;
|
||||
|
||||
// note these channels are not iterated in any particular order,
|
||||
// our current logic takes the highest permission available.
|
||||
while let Some(row) = rows.next().await {
|
||||
let (membership_channel, role, visibility): (
|
||||
ChannelId,
|
||||
ChannelRole,
|
||||
ChannelVisibility,
|
||||
) = row?;
|
||||
|
||||
match role {
|
||||
ChannelRole::Admin | ChannelRole::Member | ChannelRole::Banned => {
|
||||
if let Some(users_role) = user_role {
|
||||
user_role = Some(users_role.max(role));
|
||||
} else {
|
||||
user_role = Some(role)
|
||||
}
|
||||
}
|
||||
ChannelRole::Guest if visibility == ChannelVisibility::Public => {
|
||||
is_participant = true
|
||||
}
|
||||
ChannelRole::Guest => {}
|
||||
}
|
||||
if channel.id == membership_channel {
|
||||
current_channel_visibility = Some(visibility);
|
||||
}
|
||||
}
|
||||
// free up database connection
|
||||
drop(rows);
|
||||
|
||||
if is_participant && user_role.is_none() {
|
||||
if current_channel_visibility.is_none() {
|
||||
current_channel_visibility = channel::Entity::find()
|
||||
.filter(channel::Column::Id.eq(channel.id))
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.map(|channel| channel.visibility);
|
||||
}
|
||||
if current_channel_visibility == Some(ChannelVisibility::Public) {
|
||||
user_role = Some(ChannelRole::Guest);
|
||||
}
|
||||
if !membership.role.can_see_channel(channel.visibility) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(user_role)
|
||||
Ok(Some(membership.role))
|
||||
}
|
||||
|
||||
// Get the descendants of the given set if channels, ordered by their
|
||||
|
@ -1182,11 +925,10 @@ impl Database {
|
|||
pub async fn get_channel(&self, channel_id: ChannelId, user_id: UserId) -> Result<Channel> {
|
||||
self.transaction(|tx| async move {
|
||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||
let role = self
|
||||
.check_user_is_channel_participant(&channel, user_id, &*tx)
|
||||
self.check_user_is_channel_participant(&channel, user_id, &*tx)
|
||||
.await?;
|
||||
|
||||
Ok(Channel::from_model(channel, role))
|
||||
Ok(Channel::from_model(channel))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
@ -1243,61 +985,39 @@ impl Database {
|
|||
pub async fn move_channel(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
new_parent_id: Option<ChannelId>,
|
||||
new_parent_id: ChannelId,
|
||||
admin_id: UserId,
|
||||
) -> Result<Option<MoveChannelResult>> {
|
||||
) -> Result<(Vec<Channel>, Vec<channel_member::Model>)> {
|
||||
self.transaction(|tx| async move {
|
||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
|
||||
.await?;
|
||||
let new_parent = self.get_channel_internal(new_parent_id, &*tx).await?;
|
||||
|
||||
let new_parent_path;
|
||||
let new_parent_channel;
|
||||
if let Some(new_parent_id) = new_parent_id {
|
||||
let new_parent = self.get_channel_internal(new_parent_id, &*tx).await?;
|
||||
self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
|
||||
.await?;
|
||||
|
||||
if new_parent
|
||||
.ancestors_including_self()
|
||||
.any(|id| id == channel.id)
|
||||
{
|
||||
Err(anyhow!("cannot move a channel into one of its descendants"))?;
|
||||
}
|
||||
|
||||
new_parent_path = new_parent.path();
|
||||
new_parent_channel = Some(new_parent);
|
||||
} else {
|
||||
new_parent_path = String::new();
|
||||
new_parent_channel = None;
|
||||
};
|
||||
|
||||
let previous_participants = self
|
||||
.get_channel_participant_details_internal(&channel, &*tx)
|
||||
.await?;
|
||||
|
||||
let old_path = format!("{}{}/", channel.parent_path, channel.id);
|
||||
let new_path = format!("{}{}/", new_parent_path, channel.id);
|
||||
|
||||
if old_path == new_path {
|
||||
return Ok(None);
|
||||
if new_parent.root_id() != channel.root_id() {
|
||||
Err(anyhow!(ErrorCode::WrongMoveTarget))?;
|
||||
}
|
||||
|
||||
if new_parent
|
||||
.ancestors_including_self()
|
||||
.any(|id| id == channel.id)
|
||||
{
|
||||
Err(anyhow!(ErrorCode::CircularNesting))?;
|
||||
}
|
||||
|
||||
if channel.visibility == ChannelVisibility::Public
|
||||
&& new_parent.visibility != ChannelVisibility::Public
|
||||
{
|
||||
Err(anyhow!(ErrorCode::BadPublicNesting))?;
|
||||
}
|
||||
|
||||
let root_id = channel.root_id();
|
||||
let old_path = format!("{}{}/", channel.parent_path, channel.id);
|
||||
let new_path = format!("{}{}/", new_parent.path(), channel.id);
|
||||
|
||||
let mut model = channel.into_active_model();
|
||||
model.parent_path = ActiveValue::Set(new_parent_path);
|
||||
model.update(&*tx).await?;
|
||||
|
||||
if new_parent_channel.is_none() {
|
||||
channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel_id),
|
||||
user_id: ActiveValue::Set(admin_id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Admin),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
}
|
||||
model.parent_path = ActiveValue::Set(new_parent.path());
|
||||
let channel = model.update(&*tx).await?;
|
||||
|
||||
let descendent_ids =
|
||||
ChannelId::find_by_statement::<QueryIds>(Statement::from_sql_and_values(
|
||||
|
@ -1312,10 +1032,22 @@ impl Database {
|
|||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(Some(MoveChannelResult {
|
||||
previous_participants,
|
||||
descendent_ids,
|
||||
}))
|
||||
let all_moved_ids = Some(channel.id).into_iter().chain(descendent_ids);
|
||||
|
||||
let channels = channel::Entity::find()
|
||||
.filter(channel::Column::Id.is_in(all_moved_ids))
|
||||
.all(&*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|c| Channel::from_model(c))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let channel_members = channel_member::Entity::find()
|
||||
.filter(channel_member::Column::ChannelId.eq(root_id))
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok((channels, channel_members))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -385,25 +385,11 @@ impl Database {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the unseen messages for the given user in the specified channels.
|
||||
pub async fn unseen_channel_messages(
|
||||
pub async fn latest_channel_messages(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
channel_ids: &[ChannelId],
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::UnseenChannelMessage>> {
|
||||
let mut observed_messages_by_channel_id = HashMap::default();
|
||||
let mut rows = observed_channel_messages::Entity::find()
|
||||
.filter(observed_channel_messages::Column::UserId.eq(user_id))
|
||||
.filter(observed_channel_messages::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
observed_messages_by_channel_id.insert(row.channel_id, row);
|
||||
}
|
||||
drop(rows);
|
||||
) -> Result<Vec<proto::ChannelMessageId>> {
|
||||
let mut values = String::new();
|
||||
for id in channel_ids {
|
||||
if !values.is_empty() {
|
||||
|
@ -413,7 +399,7 @@ impl Database {
|
|||
}
|
||||
|
||||
if values.is_empty() {
|
||||
return Ok(Default::default());
|
||||
return Ok(Vec::default());
|
||||
}
|
||||
|
||||
let sql = format!(
|
||||
|
@ -437,26 +423,20 @@ impl Database {
|
|||
);
|
||||
|
||||
let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
|
||||
let last_messages = channel_message::Model::find_by_statement(stmt)
|
||||
.all(&*tx)
|
||||
let mut last_messages = channel_message::Model::find_by_statement(stmt)
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
|
||||
let mut changes = Vec::new();
|
||||
for last_message in last_messages {
|
||||
if let Some(observed_message) =
|
||||
observed_messages_by_channel_id.get(&last_message.channel_id)
|
||||
{
|
||||
if observed_message.channel_message_id == last_message.id {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
changes.push(proto::UnseenChannelMessage {
|
||||
channel_id: last_message.channel_id.to_proto(),
|
||||
message_id: last_message.id.to_proto(),
|
||||
let mut results = Vec::new();
|
||||
while let Some(result) = last_messages.next().await {
|
||||
let message = result?;
|
||||
results.push(proto::ChannelMessageId {
|
||||
channel_id: message.channel_id.to_proto(),
|
||||
message_id: message.id.to_proto(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(changes)
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Removes the channel message with the given ID.
|
||||
|
|
|
@ -17,6 +17,14 @@ impl Model {
|
|||
self.ancestors().last()
|
||||
}
|
||||
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.parent_path.is_empty()
|
||||
}
|
||||
|
||||
pub fn root_id(&self) -> ChannelId {
|
||||
self.ancestors().next().unwrap_or(self.id)
|
||||
}
|
||||
|
||||
pub fn ancestors(&self) -> impl Iterator<Item = ChannelId> + '_ {
|
||||
self.parent_path
|
||||
.trim_end_matches('/')
|
||||
|
|
|
@ -150,14 +150,13 @@ impl Drop for TestDb {
|
|||
}
|
||||
}
|
||||
|
||||
fn channel_tree(channels: &[(ChannelId, &[ChannelId], &'static str, ChannelRole)]) -> Vec<Channel> {
|
||||
fn channel_tree(channels: &[(ChannelId, &[ChannelId], &'static str)]) -> Vec<Channel> {
|
||||
channels
|
||||
.iter()
|
||||
.map(|(id, parent_path, name, role)| Channel {
|
||||
.map(|(id, parent_path, name)| Channel {
|
||||
id: *id,
|
||||
name: name.to_string(),
|
||||
visibility: ChannelVisibility::Members,
|
||||
role: *role,
|
||||
parent_path: parent_path.to_vec(),
|
||||
})
|
||||
.collect()
|
||||
|
|
|
@ -330,8 +330,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
|||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.unseen_channel_buffer_changes(
|
||||
observer_id,
|
||||
db.latest_channel_buffer_changes(
|
||||
&[
|
||||
buffers[0].channel_id,
|
||||
buffers[1].channel_id,
|
||||
|
@ -348,12 +347,12 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
|||
pretty_assertions::assert_eq!(
|
||||
buffer_changes,
|
||||
[
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
rpc::proto::ChannelBufferVersion {
|
||||
channel_id: buffers[0].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[0].version()),
|
||||
},
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
rpc::proto::ChannelBufferVersion {
|
||||
channel_id: buffers[1].channel_id.to_proto(),
|
||||
epoch: 1,
|
||||
version: serialize_version(&text_buffers[1].version())
|
||||
|
@ -362,99 +361,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
|||
== buffer_changes[1].version.first().unwrap().replica_id)
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[2].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[2].version()),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
db.observe_buffer_version(
|
||||
buffers[1].id,
|
||||
observer_id,
|
||||
1,
|
||||
serialize_version(&text_buffers[1].version()).as_slice(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let buffer_changes = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.unseen_channel_buffer_changes(
|
||||
observer_id,
|
||||
&[
|
||||
buffers[0].channel_id,
|
||||
buffers[1].channel_id,
|
||||
buffers[2].channel_id,
|
||||
],
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
buffer_changes,
|
||||
[
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[0].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[0].version()),
|
||||
},
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[2].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[2].version()),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Observe an earlier version of the buffer.
|
||||
db.observe_buffer_version(
|
||||
buffers[1].id,
|
||||
observer_id,
|
||||
1,
|
||||
&[rpc::proto::VectorClockEntry {
|
||||
replica_id: 0,
|
||||
timestamp: 0,
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let buffer_changes = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.unseen_channel_buffer_changes(
|
||||
observer_id,
|
||||
&[
|
||||
buffers[0].channel_id,
|
||||
buffers[1].channel_id,
|
||||
buffers[2].channel_id,
|
||||
],
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
buffer_changes,
|
||||
[
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
channel_id: buffers[0].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[0].version()),
|
||||
},
|
||||
rpc::proto::UnseenChannelBufferChange {
|
||||
rpc::proto::ChannelBufferVersion {
|
||||
channel_id: buffers[2].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[2].version()),
|
||||
|
|
|
@ -62,23 +62,13 @@ async fn test_channels(db: &Arc<Database>) {
|
|||
assert_eq!(
|
||||
result.channels,
|
||||
channel_tree(&[
|
||||
(zed_id, &[], "zed", ChannelRole::Admin),
|
||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Admin),
|
||||
(
|
||||
livestreaming_id,
|
||||
&[zed_id],
|
||||
"livestreaming",
|
||||
ChannelRole::Admin
|
||||
),
|
||||
(replace_id, &[zed_id], "replace", ChannelRole::Admin),
|
||||
(rust_id, &[], "rust", ChannelRole::Admin),
|
||||
(cargo_id, &[rust_id], "cargo", ChannelRole::Admin),
|
||||
(
|
||||
cargo_ra_id,
|
||||
&[rust_id, cargo_id],
|
||||
"cargo-ra",
|
||||
ChannelRole::Admin
|
||||
)
|
||||
(zed_id, &[], "zed"),
|
||||
(crdb_id, &[zed_id], "crdb"),
|
||||
(livestreaming_id, &[zed_id], "livestreaming",),
|
||||
(replace_id, &[zed_id], "replace"),
|
||||
(rust_id, &[], "rust"),
|
||||
(cargo_id, &[rust_id], "cargo"),
|
||||
(cargo_ra_id, &[rust_id, cargo_id], "cargo-ra",)
|
||||
],)
|
||||
);
|
||||
|
||||
|
@ -86,15 +76,10 @@ async fn test_channels(db: &Arc<Database>) {
|
|||
assert_eq!(
|
||||
result.channels,
|
||||
channel_tree(&[
|
||||
(zed_id, &[], "zed", ChannelRole::Member),
|
||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Member),
|
||||
(
|
||||
livestreaming_id,
|
||||
&[zed_id],
|
||||
"livestreaming",
|
||||
ChannelRole::Member
|
||||
),
|
||||
(replace_id, &[zed_id], "replace", ChannelRole::Member)
|
||||
(zed_id, &[], "zed"),
|
||||
(crdb_id, &[zed_id], "crdb"),
|
||||
(livestreaming_id, &[zed_id], "livestreaming",),
|
||||
(replace_id, &[zed_id], "replace")
|
||||
],)
|
||||
);
|
||||
|
||||
|
@ -112,15 +97,10 @@ async fn test_channels(db: &Arc<Database>) {
|
|||
assert_eq!(
|
||||
result.channels,
|
||||
channel_tree(&[
|
||||
(zed_id, &[], "zed", ChannelRole::Admin),
|
||||
(crdb_id, &[zed_id], "crdb", ChannelRole::Admin),
|
||||
(
|
||||
livestreaming_id,
|
||||
&[zed_id],
|
||||
"livestreaming",
|
||||
ChannelRole::Admin
|
||||
),
|
||||
(replace_id, &[zed_id], "replace", ChannelRole::Admin)
|
||||
(zed_id, &[], "zed"),
|
||||
(crdb_id, &[zed_id], "crdb"),
|
||||
(livestreaming_id, &[zed_id], "livestreaming",),
|
||||
(replace_id, &[zed_id], "replace")
|
||||
],)
|
||||
);
|
||||
|
||||
|
@ -271,14 +251,19 @@ async fn test_channel_invites(db: &Arc<Database>) {
|
|||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: user_1.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: user_2.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: user_3.to_proto(),
|
||||
kind: proto::channel_member::Kind::Invitee.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -420,13 +405,6 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
// Move to same parent should be a no-op
|
||||
assert!(db
|
||||
.move_channel(projects_id, Some(zed_id), user_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
let result = db.get_channels_for_user(user_id).await.unwrap();
|
||||
assert_channel_tree(
|
||||
result.channels,
|
||||
|
@ -437,20 +415,8 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
|
|||
],
|
||||
);
|
||||
|
||||
// Move the project channel to the root
|
||||
db.move_channel(projects_id, None, user_id).await.unwrap();
|
||||
let result = db.get_channels_for_user(user_id).await.unwrap();
|
||||
assert_channel_tree(
|
||||
result.channels,
|
||||
&[
|
||||
(zed_id, &[]),
|
||||
(projects_id, &[]),
|
||||
(livestreaming_id, &[projects_id]),
|
||||
],
|
||||
);
|
||||
|
||||
// Can't move a channel into its ancestor
|
||||
db.move_channel(projects_id, Some(livestreaming_id), user_id)
|
||||
db.move_channel(projects_id, livestreaming_id, user_id)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let result = db.get_channels_for_user(user_id).await.unwrap();
|
||||
|
@ -458,8 +424,8 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
|
|||
result.channels,
|
||||
&[
|
||||
(zed_id, &[]),
|
||||
(projects_id, &[]),
|
||||
(livestreaming_id, &[projects_id]),
|
||||
(projects_id, &[zed_id]),
|
||||
(livestreaming_id, &[zed_id, projects_id]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -476,32 +442,39 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
let guest = new_test_user(db, "guest@example.com").await;
|
||||
|
||||
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
|
||||
let active_channel_id = db
|
||||
let internal_channel_id = db
|
||||
.create_sub_channel("active", zed_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
let vim_channel_id = db
|
||||
.create_sub_channel("vim", active_channel_id, admin)
|
||||
let public_channel_id = db
|
||||
.create_sub_channel("vim", zed_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(vim_channel_id, crate::db::ChannelVisibility::Public, admin)
|
||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(active_channel_id, member, admin, ChannelRole::Member)
|
||||
db.set_channel_visibility(
|
||||
public_channel_id,
|
||||
crate::db::ChannelVisibility::Public,
|
||||
admin,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(zed_channel, member, admin, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(vim_channel_id, guest, admin, ChannelRole::Guest)
|
||||
db.invite_channel_member(zed_channel, guest, admin, ChannelRole::Guest)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.respond_to_channel_invite(active_channel_id, member, true)
|
||||
db.respond_to_channel_invite(zed_channel, member, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
||||
&db.get_channel_internal(public_channel_id, &*tx).await?,
|
||||
admin,
|
||||
&*tx,
|
||||
)
|
||||
|
@ -511,7 +484,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
||||
&db.get_channel_internal(public_channel_id, &*tx).await?,
|
||||
member,
|
||||
&*tx,
|
||||
)
|
||||
|
@ -521,7 +494,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
|
||||
let mut members = db
|
||||
.get_channel_participant_details(vim_channel_id, admin)
|
||||
.get_channel_participant_details(public_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -532,12 +505,12 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: admin.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: member.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
|
@ -548,13 +521,13 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
]
|
||||
);
|
||||
|
||||
db.respond_to_channel_invite(vim_channel_id, guest, true)
|
||||
db.respond_to_channel_invite(zed_channel, guest, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await?,
|
||||
&db.get_channel_internal(public_channel_id, &*tx).await?,
|
||||
guest,
|
||||
&*tx,
|
||||
)
|
||||
|
@ -564,23 +537,29 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
|
||||
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
|
||||
assert_channel_tree(channels, &[(vim_channel_id, &[])]);
|
||||
assert_channel_tree(
|
||||
channels,
|
||||
&[(zed_channel, &[]), (public_channel_id, &[zed_channel])],
|
||||
);
|
||||
let channels = db.get_channels_for_user(member).await.unwrap().channels;
|
||||
assert_channel_tree(
|
||||
channels,
|
||||
&[
|
||||
(active_channel_id, &[]),
|
||||
(vim_channel_id, &[active_channel_id]),
|
||||
(zed_channel, &[]),
|
||||
(internal_channel_id, &[zed_channel]),
|
||||
(public_channel_id, &[zed_channel]),
|
||||
],
|
||||
);
|
||||
|
||||
db.set_channel_member_role(vim_channel_id, admin, guest, ChannelRole::Banned)
|
||||
db.set_channel_member_role(zed_channel, admin, guest, ChannelRole::Banned)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(db
|
||||
.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await.unwrap(),
|
||||
&db.get_channel_internal(public_channel_id, &*tx)
|
||||
.await
|
||||
.unwrap(),
|
||||
guest,
|
||||
&*tx,
|
||||
)
|
||||
|
@ -590,7 +569,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
.is_err());
|
||||
|
||||
let mut members = db
|
||||
.get_channel_participant_details(vim_channel_id, admin)
|
||||
.get_channel_participant_details(public_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -601,12 +580,12 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: admin.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: member.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
|
@ -617,11 +596,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
]
|
||||
);
|
||||
|
||||
db.remove_channel_member(vim_channel_id, guest, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
db.remove_channel_member(zed_channel, guest, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -631,7 +606,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
|
||||
// currently people invited to parent channels are not shown here
|
||||
let mut members = db
|
||||
.get_channel_participant_details(vim_channel_id, admin)
|
||||
.get_channel_participant_details(public_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -642,14 +617,19 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: admin.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: member.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: guest.to_proto(),
|
||||
kind: proto::channel_member::Kind::Invitee.into(),
|
||||
role: proto::ChannelRole::Guest.into(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -670,7 +650,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
assert!(db
|
||||
.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(active_channel_id, &*tx)
|
||||
&db.get_channel_internal(internal_channel_id, &*tx)
|
||||
.await
|
||||
.unwrap(),
|
||||
guest,
|
||||
|
@ -683,7 +663,9 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
|
||||
db.transaction(|tx| async move {
|
||||
db.check_user_is_channel_participant(
|
||||
&db.get_channel_internal(vim_channel_id, &*tx).await.unwrap(),
|
||||
&db.get_channel_internal(public_channel_id, &*tx)
|
||||
.await
|
||||
.unwrap(),
|
||||
guest,
|
||||
&*tx,
|
||||
)
|
||||
|
@ -693,7 +675,7 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
|
||||
let mut members = db
|
||||
.get_channel_participant_details(vim_channel_id, admin)
|
||||
.get_channel_participant_details(public_channel_id, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -704,17 +686,17 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
&[
|
||||
proto::ChannelMember {
|
||||
user_id: admin.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Admin.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: member.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Member.into(),
|
||||
},
|
||||
proto::ChannelMember {
|
||||
user_id: guest.to_proto(),
|
||||
kind: proto::channel_member::Kind::AncestorMember.into(),
|
||||
kind: proto::channel_member::Kind::Member.into(),
|
||||
role: proto::ChannelRole::Guest.into(),
|
||||
},
|
||||
]
|
||||
|
@ -723,67 +705,10 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
|||
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
|
||||
assert_channel_tree(
|
||||
channels,
|
||||
&[(zed_channel, &[]), (vim_channel_id, &[zed_channel])],
|
||||
&[(zed_channel, &[]), (public_channel_id, &[zed_channel])],
|
||||
)
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_user_joins_correct_channel,
|
||||
test_user_joins_correct_channel_postgres,
|
||||
test_user_joins_correct_channel_sqlite
|
||||
);
|
||||
|
||||
async fn test_user_joins_correct_channel(db: &Arc<Database>) {
|
||||
let admin = new_test_user(db, "admin@example.com").await;
|
||||
|
||||
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
|
||||
|
||||
let active_channel = db
|
||||
.create_sub_channel("active", zed_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let vim_channel = db
|
||||
.create_sub_channel("vim", active_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let vim2_channel = db
|
||||
.create_sub_channel("vim2", vim_channel, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(vim_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(vim2_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let most_public = db
|
||||
.transaction(|tx| async move {
|
||||
Ok(db
|
||||
.public_ancestors_including_self(
|
||||
&db.get_channel_internal(vim_channel, &*tx).await.unwrap(),
|
||||
&tx,
|
||||
)
|
||||
.await?
|
||||
.first()
|
||||
.cloned())
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.id;
|
||||
|
||||
assert_eq!(most_public, zed_channel)
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_guest_access,
|
||||
test_guest_access_postgres,
|
||||
|
|
|
@ -15,7 +15,7 @@ test_both_dbs!(
|
|||
|
||||
async fn test_channel_message_retrieval(db: &Arc<Database>) {
|
||||
let user = new_test_user(db, "user@example.com").await;
|
||||
let channel = db.create_channel("channel", None, user).await.unwrap();
|
||||
let channel = db.create_channel("channel", None, user).await.unwrap().0;
|
||||
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
db.join_channel_chat(channel.id, rpc::ConnectionId { owner_id, id: 0 }, user)
|
||||
|
@ -235,11 +235,10 @@ async fn test_unseen_channel_messages(db: &Arc<Database>) {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let second_message = db
|
||||
let _ = db
|
||||
.create_channel_message(channel_1, user, "1_2", &[], OffsetDateTime::now_utc(), 2)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
.unwrap();
|
||||
|
||||
let third_message = db
|
||||
.create_channel_message(channel_1, user, "1_3", &[], OffsetDateTime::now_utc(), 3)
|
||||
|
@ -258,97 +257,27 @@ async fn test_unseen_channel_messages(db: &Arc<Database>) {
|
|||
.message_id;
|
||||
|
||||
// Check that observer has new messages
|
||||
let unseen_messages = db
|
||||
let latest_messages = db
|
||||
.transaction(|tx| async move {
|
||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
||||
db.latest_channel_messages(&[channel_1, channel_2], &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
unseen_messages,
|
||||
latest_messages,
|
||||
[
|
||||
rpc::proto::UnseenChannelMessage {
|
||||
rpc::proto::ChannelMessageId {
|
||||
channel_id: channel_1.to_proto(),
|
||||
message_id: third_message.to_proto(),
|
||||
},
|
||||
rpc::proto::UnseenChannelMessage {
|
||||
rpc::proto::ChannelMessageId {
|
||||
channel_id: channel_2.to_proto(),
|
||||
message_id: fourth_message.to_proto(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Observe the second message
|
||||
db.observe_channel_message(channel_1, observer, second_message)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the observer still has a new message
|
||||
let unseen_messages = db
|
||||
.transaction(|tx| async move {
|
||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
unseen_messages,
|
||||
[
|
||||
rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_1.to_proto(),
|
||||
message_id: third_message.to_proto(),
|
||||
},
|
||||
rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_2.to_proto(),
|
||||
message_id: fourth_message.to_proto(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Observe the third message,
|
||||
db.observe_channel_message(channel_1, observer, third_message)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the observer does not have a new method
|
||||
let unseen_messages = db
|
||||
.transaction(|tx| async move {
|
||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
unseen_messages,
|
||||
[rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_2.to_proto(),
|
||||
message_id: fourth_message.to_proto(),
|
||||
}]
|
||||
);
|
||||
|
||||
// Observe the second message again, should not regress our observed state
|
||||
db.observe_channel_message(channel_1, observer, second_message)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make sure the observer does not have a new message
|
||||
let unseen_messages = db
|
||||
.transaction(|tx| async move {
|
||||
db.unseen_channel_messages(observer, &[channel_1, channel_2], &*tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
unseen_messages,
|
||||
[rpc::proto::UnseenChannelMessage {
|
||||
channel_id: channel_2.to_proto(),
|
||||
message_id: fourth_message.to_proto(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
|
@ -362,7 +291,12 @@ async fn test_channel_message_mentions(db: &Arc<Database>) {
|
|||
let user_b = new_test_user(db, "user_b@example.com").await;
|
||||
let user_c = new_test_user(db, "user_c@example.com").await;
|
||||
|
||||
let channel = db.create_channel("channel", None, user_a).await.unwrap().id;
|
||||
let channel = db
|
||||
.create_channel("channel", None, user_a)
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
.id;
|
||||
db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member)
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
@ -5,8 +5,7 @@ use crate::{
|
|||
db::{
|
||||
self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database,
|
||||
InviteMemberResult, MembershipUpdated, MessageId, NotificationId, ProjectId,
|
||||
RemoveChannelMemberResult, RenameChannelResult, RespondToChannelInvite, RoomId, ServerId,
|
||||
SetChannelVisibilityResult, User, UserId,
|
||||
RemoveChannelMemberResult, RespondToChannelInvite, RoomId, ServerId, User, UserId,
|
||||
},
|
||||
executor::Executor,
|
||||
AppState, Error, Result,
|
||||
|
@ -602,6 +601,7 @@ impl Server {
|
|||
let mut pool = this.connection_pool.lock();
|
||||
pool.add_connection(connection_id, user_id, user.admin);
|
||||
this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
|
||||
this.peer.send(connection_id, build_update_user_channels(&channels_for_user.channel_memberships))?;
|
||||
this.peer.send(connection_id, build_channels_update(
|
||||
channels_for_user,
|
||||
channel_invites
|
||||
|
@ -2300,7 +2300,7 @@ async fn create_channel(
|
|||
let db = session.db().await;
|
||||
|
||||
let parent_id = request.parent_id.map(|id| ChannelId::from_proto(id));
|
||||
let channel = db
|
||||
let (channel, owner, channel_members) = db
|
||||
.create_channel(&request.name, parent_id, session.user_id)
|
||||
.await?;
|
||||
|
||||
|
@ -2309,20 +2309,30 @@ async fn create_channel(
|
|||
parent_id: request.parent_id,
|
||||
})?;
|
||||
|
||||
let participants_to_update;
|
||||
if let Some(parent) = parent_id {
|
||||
participants_to_update = db.new_participants_to_notify(parent).await?;
|
||||
} else {
|
||||
participants_to_update = vec![];
|
||||
let connection_pool = session.connection_pool().await;
|
||||
if let Some(owner) = owner {
|
||||
let update = proto::UpdateUserChannels {
|
||||
channel_memberships: vec![proto::ChannelMembership {
|
||||
channel_id: owner.channel_id.to_proto(),
|
||||
role: owner.role.into(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
for connection_id in connection_pool.user_connection_ids(owner.user_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
let connection_pool = session.connection_pool().await;
|
||||
for (user_id, channels) in participants_to_update {
|
||||
let update = build_channels_update(channels, vec![]);
|
||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
||||
if user_id == session.user_id {
|
||||
continue;
|
||||
}
|
||||
for channel_member in channel_members {
|
||||
if !channel_member.role.can_see_channel(channel.visibility) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let update = proto::UpdateChannels {
|
||||
channels: vec![channel.to_proto()],
|
||||
..Default::default()
|
||||
};
|
||||
for connection_id in connection_pool.user_connection_ids(channel_member.user_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
@ -2439,7 +2449,9 @@ async fn remove_channel_member(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Toggle the channel between public and private
|
||||
/// Toggle the channel between public and private.
|
||||
/// Care is taken to maintain the invariant that public channels only descend from public channels,
|
||||
/// (though members-only channels can appear at any point in the hierarchy).
|
||||
async fn set_channel_visibility(
|
||||
request: proto::SetChannelVisibility,
|
||||
response: Response<proto::SetChannelVisibility>,
|
||||
|
@ -2449,27 +2461,25 @@ async fn set_channel_visibility(
|
|||
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||
let visibility = request.visibility().into();
|
||||
|
||||
let SetChannelVisibilityResult {
|
||||
participants_to_update,
|
||||
participants_to_remove,
|
||||
channels_to_remove,
|
||||
} = db
|
||||
let (channel, channel_members) = db
|
||||
.set_channel_visibility(channel_id, visibility, session.user_id)
|
||||
.await?;
|
||||
|
||||
let connection_pool = session.connection_pool().await;
|
||||
for (user_id, channels) in participants_to_update {
|
||||
let update = build_channels_update(channels, vec![]);
|
||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
for user_id in participants_to_remove {
|
||||
let update = proto::UpdateChannels {
|
||||
delete_channels: channels_to_remove.iter().map(|id| id.to_proto()).collect(),
|
||||
..Default::default()
|
||||
for member in channel_members {
|
||||
let update = if member.role.can_see_channel(channel.visibility) {
|
||||
proto::UpdateChannels {
|
||||
channels: vec![channel.to_proto()],
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
proto::UpdateChannels {
|
||||
delete_channels: vec![channel.id.to_proto()],
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
||||
|
||||
for connection_id in connection_pool.user_connection_ids(member.user_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
@ -2478,7 +2488,7 @@ async fn set_channel_visibility(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Alter the role for a user in the channel
|
||||
/// Alter the role for a user in the channel.
|
||||
async fn set_channel_member_role(
|
||||
request: proto::SetChannelMemberRole,
|
||||
response: Response<proto::SetChannelMemberRole>,
|
||||
|
@ -2534,10 +2544,7 @@ async fn rename_channel(
|
|||
) -> Result<()> {
|
||||
let db = session.db().await;
|
||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||
let RenameChannelResult {
|
||||
channel,
|
||||
participants_to_update,
|
||||
} = db
|
||||
let (channel, channel_members) = db
|
||||
.rename_channel(channel_id, session.user_id, &request.name)
|
||||
.await?;
|
||||
|
||||
|
@ -2546,13 +2553,15 @@ async fn rename_channel(
|
|||
})?;
|
||||
|
||||
let connection_pool = session.connection_pool().await;
|
||||
for (user_id, channel) in participants_to_update {
|
||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
||||
let update = proto::UpdateChannels {
|
||||
channels: vec![channel.to_proto()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for channel_member in channel_members {
|
||||
if !channel_member.role.can_see_channel(channel.visibility) {
|
||||
continue;
|
||||
}
|
||||
let update = proto::UpdateChannels {
|
||||
channels: vec![channel.to_proto()],
|
||||
..Default::default()
|
||||
};
|
||||
for connection_id in connection_pool.user_connection_ids(channel_member.user_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
@ -2567,57 +2576,37 @@ async fn move_channel(
|
|||
session: Session,
|
||||
) -> Result<()> {
|
||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||
let to = request.to.map(ChannelId::from_proto);
|
||||
let to = ChannelId::from_proto(request.to);
|
||||
|
||||
let result = session
|
||||
let (channels, channel_members) = session
|
||||
.db()
|
||||
.await
|
||||
.move_channel(channel_id, to, session.user_id)
|
||||
.await?;
|
||||
|
||||
if let Some(result) = result {
|
||||
let participants_to_update: HashMap<_, _> = session
|
||||
.db()
|
||||
.await
|
||||
.new_participants_to_notify(to.unwrap_or(channel_id))
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let mut moved_channels: HashSet<ChannelId> = HashSet::default();
|
||||
for id in result.descendent_ids {
|
||||
moved_channels.insert(id);
|
||||
}
|
||||
moved_channels.insert(channel_id);
|
||||
|
||||
let mut participants_to_remove: HashSet<UserId> = HashSet::default();
|
||||
for participant in result.previous_participants {
|
||||
if participant.kind == proto::channel_member::Kind::AncestorMember {
|
||||
if !participants_to_update.contains_key(&participant.user_id) {
|
||||
participants_to_remove.insert(participant.user_id);
|
||||
let connection_pool = session.connection_pool().await;
|
||||
for member in channel_members {
|
||||
let channels = channels
|
||||
.iter()
|
||||
.filter_map(|channel| {
|
||||
if member.role.can_see_channel(channel.visibility) {
|
||||
Some(channel.to_proto())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if channels.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let moved_channels: Vec<u64> = moved_channels.iter().map(|id| id.to_proto()).collect();
|
||||
let update = proto::UpdateChannels {
|
||||
channels,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let connection_pool = session.connection_pool().await;
|
||||
for (user_id, channels) in participants_to_update {
|
||||
let mut update = build_channels_update(channels, vec![]);
|
||||
update.delete_channels = moved_channels.clone();
|
||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
for user_id in participants_to_remove {
|
||||
let update = proto::UpdateChannels {
|
||||
delete_channels: moved_channels.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
for connection_id in connection_pool.user_connection_ids(member.user_id) {
|
||||
session.peer.send(connection_id, update.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2851,7 +2840,7 @@ async fn update_channel_buffer(
|
|||
session.peer.send(
|
||||
peer_id.into(),
|
||||
proto::UpdateChannels {
|
||||
unseen_channel_buffer_changes: vec![proto::UnseenChannelBufferChange {
|
||||
latest_channel_buffer_versions: vec![proto::ChannelBufferVersion {
|
||||
channel_id: channel_id.to_proto(),
|
||||
epoch: epoch as u64,
|
||||
version: version.clone(),
|
||||
|
@ -3037,7 +3026,7 @@ async fn send_channel_message(
|
|||
session.peer.send(
|
||||
peer_id.into(),
|
||||
proto::UpdateChannels {
|
||||
unseen_channel_messages: vec![proto::UnseenChannelMessage {
|
||||
latest_channel_message_ids: vec![proto::ChannelMessageId {
|
||||
channel_id: channel_id.to_proto(),
|
||||
message_id: message_id.to_proto(),
|
||||
}],
|
||||
|
@ -3292,6 +3281,18 @@ fn notify_membership_updated(
|
|||
user_id: UserId,
|
||||
peer: &Peer,
|
||||
) {
|
||||
let user_channels_update = proto::UpdateUserChannels {
|
||||
channel_memberships: result
|
||||
.new_channels
|
||||
.channel_memberships
|
||||
.iter()
|
||||
.map(|cm| proto::ChannelMembership {
|
||||
channel_id: cm.channel_id.to_proto(),
|
||||
role: cm.role.into(),
|
||||
})
|
||||
.collect(),
|
||||
..Default::default()
|
||||
};
|
||||
let mut update = build_channels_update(result.new_channels, vec![]);
|
||||
update.delete_channels = result
|
||||
.removed_channels
|
||||
|
@ -3301,10 +3302,27 @@ fn notify_membership_updated(
|
|||
update.remove_channel_invitations = vec![result.channel_id.to_proto()];
|
||||
|
||||
for connection_id in connection_pool.user_connection_ids(user_id) {
|
||||
peer.send(connection_id, user_channels_update.clone())
|
||||
.trace_err();
|
||||
peer.send(connection_id, update.clone()).trace_err();
|
||||
}
|
||||
}
|
||||
|
||||
fn build_update_user_channels(
|
||||
memberships: &Vec<db::channel_member::Model>,
|
||||
) -> proto::UpdateUserChannels {
|
||||
proto::UpdateUserChannels {
|
||||
channel_memberships: memberships
|
||||
.iter()
|
||||
.map(|m| proto::ChannelMembership {
|
||||
channel_id: m.channel_id.to_proto(),
|
||||
role: m.role.into(),
|
||||
})
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn build_channels_update(
|
||||
channels: ChannelsForUser,
|
||||
channel_invites: Vec<db::Channel>,
|
||||
|
@ -3315,8 +3333,8 @@ fn build_channels_update(
|
|||
update.channels.push(channel.to_proto());
|
||||
}
|
||||
|
||||
update.unseen_channel_buffer_changes = channels.unseen_buffer_changes;
|
||||
update.unseen_channel_messages = channels.channel_messages;
|
||||
update.latest_channel_buffer_versions = channels.latest_buffer_versions;
|
||||
update.latest_channel_message_ids = channels.latest_channel_messages;
|
||||
|
||||
for (channel_id, participants) in channels.channel_participants {
|
||||
update
|
||||
|
|
|
@ -637,7 +637,6 @@ async fn test_channel_buffer_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
assert!(has_buffer_changed);
|
||||
|
||||
|
@ -655,7 +654,6 @@ async fn test_channel_buffer_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
|
@ -672,7 +670,6 @@ async fn test_channel_buffer_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
|
@ -687,7 +684,6 @@ async fn test_channel_buffer_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
|
@ -714,7 +710,6 @@ async fn test_channel_buffer_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
assert!(has_buffer_changed);
|
||||
}
|
||||
|
|
|
@ -195,6 +195,13 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||
})
|
||||
.await
|
||||
.unwrap();
|
||||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |store, cx| {
|
||||
store.set_channel_visibility(parent_channel_id, proto::ChannelVisibility::Public, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |store, cx| {
|
||||
|
|
|
@ -313,7 +313,6 @@ async fn test_channel_message_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_new_messages(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
assert!(b_has_messages);
|
||||
|
@ -341,7 +340,6 @@ async fn test_channel_message_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_new_messages(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
assert!(!b_has_messages);
|
||||
|
@ -359,7 +357,6 @@ async fn test_channel_message_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_new_messages(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
assert!(!b_has_messages);
|
||||
|
@ -382,7 +379,6 @@ async fn test_channel_message_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_new_messages(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
assert!(b_has_messages);
|
||||
|
@ -402,7 +398,6 @@ async fn test_channel_message_changes(
|
|||
.channel_store()
|
||||
.read(cx)
|
||||
.has_new_messages(channel_id)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
assert!(b_has_messages);
|
||||
|
|
|
@ -48,13 +48,11 @@ async fn test_core_channels(
|
|||
id: channel_a_id,
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_b_id,
|
||||
name: "channel-b".into(),
|
||||
depth: 1,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
@ -94,7 +92,6 @@ async fn test_core_channels(
|
|||
id: channel_a_id,
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
|
||||
|
@ -141,13 +138,11 @@ async fn test_core_channels(
|
|||
ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 0,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_b_id,
|
||||
name: "channel-b".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 1,
|
||||
},
|
||||
],
|
||||
|
@ -169,19 +164,16 @@ async fn test_core_channels(
|
|||
ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 0,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_b_id,
|
||||
name: "channel-b".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 1,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_c_id,
|
||||
name: "channel-c".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 2,
|
||||
},
|
||||
],
|
||||
|
@ -213,19 +205,16 @@ async fn test_core_channels(
|
|||
id: channel_a_id,
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_b_id,
|
||||
name: "channel-b".into(),
|
||||
depth: 1,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_c_id,
|
||||
name: "channel-c".into(),
|
||||
depth: 2,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
@ -247,7 +236,6 @@ async fn test_core_channels(
|
|||
id: channel_a_id,
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
);
|
||||
assert_channels(
|
||||
|
@ -257,7 +245,6 @@ async fn test_core_channels(
|
|||
id: channel_a_id,
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
);
|
||||
|
||||
|
@ -280,7 +267,6 @@ async fn test_core_channels(
|
|||
id: channel_a_id,
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
);
|
||||
|
||||
|
@ -311,7 +297,6 @@ async fn test_core_channels(
|
|||
id: channel_a_id,
|
||||
name: "channel-a-renamed".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
@ -420,7 +405,6 @@ async fn test_channel_room(
|
|||
id: zed_id,
|
||||
name: "zed".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
cx_b.read(|cx| {
|
||||
|
@ -681,7 +665,6 @@ async fn test_permissions_update_while_invited(
|
|||
depth: 0,
|
||||
id: rust_id,
|
||||
name: "rust".into(),
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
assert_channels(client_b.channel_store(), cx_b, &[]);
|
||||
|
@ -709,7 +692,6 @@ async fn test_permissions_update_while_invited(
|
|||
depth: 0,
|
||||
id: rust_id,
|
||||
name: "rust".into(),
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
assert_channels(client_b.channel_store(), cx_b, &[]);
|
||||
|
@ -748,7 +730,6 @@ async fn test_channel_rename(
|
|||
depth: 0,
|
||||
id: rust_id,
|
||||
name: "rust-archive".into(),
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
);
|
||||
|
||||
|
@ -760,7 +741,6 @@ async fn test_channel_rename(
|
|||
depth: 0,
|
||||
id: rust_id,
|
||||
name: "rust-archive".into(),
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
@ -889,7 +869,6 @@ async fn test_lost_channel_creation(
|
|||
depth: 0,
|
||||
id: channel_id,
|
||||
name: "x".into(),
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
|
||||
|
@ -913,13 +892,11 @@ async fn test_lost_channel_creation(
|
|||
depth: 0,
|
||||
id: channel_id,
|
||||
name: "x".into(),
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
ExpectedChannel {
|
||||
depth: 1,
|
||||
id: subchannel_id,
|
||||
name: "subchannel".into(),
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
@ -944,13 +921,11 @@ async fn test_lost_channel_creation(
|
|||
depth: 0,
|
||||
id: channel_id,
|
||||
name: "x".into(),
|
||||
role: ChannelRole::Member,
|
||||
},
|
||||
ExpectedChannel {
|
||||
depth: 1,
|
||||
id: subchannel_id,
|
||||
name: "subchannel".into(),
|
||||
role: ChannelRole::Member,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
@ -1035,7 +1010,7 @@ async fn test_channel_link_notifications(
|
|||
let vim_channel = client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.create_channel("vim", None, cx)
|
||||
channel_store.create_channel("vim", Some(zed_channel), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -1048,26 +1023,16 @@ async fn test_channel_link_notifications(
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.move_channel(vim_channel, Some(active_channel), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
// the new channel shows for b and c
|
||||
assert_channels_list_shape(
|
||||
client_a.channel_store(),
|
||||
cx_a,
|
||||
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 2)],
|
||||
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 1)],
|
||||
);
|
||||
assert_channels_list_shape(
|
||||
client_b.channel_store(),
|
||||
cx_b,
|
||||
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 2)],
|
||||
&[(zed_channel, 0), (active_channel, 1), (vim_channel, 1)],
|
||||
);
|
||||
assert_channels_list_shape(
|
||||
client_c.channel_store(),
|
||||
|
@ -1078,7 +1043,7 @@ async fn test_channel_link_notifications(
|
|||
let helix_channel = client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.create_channel("helix", None, cx)
|
||||
channel_store.create_channel("helix", Some(zed_channel), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -1086,7 +1051,7 @@ async fn test_channel_link_notifications(
|
|||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.move_channel(helix_channel, Some(vim_channel), cx)
|
||||
channel_store.move_channel(helix_channel, vim_channel, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -1102,6 +1067,7 @@ async fn test_channel_link_notifications(
|
|||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.run_until_parked();
|
||||
|
||||
// the new channel shows for b and c
|
||||
assert_channels_list_shape(
|
||||
|
@ -1110,8 +1076,8 @@ async fn test_channel_link_notifications(
|
|||
&[
|
||||
(zed_channel, 0),
|
||||
(active_channel, 1),
|
||||
(vim_channel, 2),
|
||||
(helix_channel, 3),
|
||||
(vim_channel, 1),
|
||||
(helix_channel, 2),
|
||||
],
|
||||
);
|
||||
assert_channels_list_shape(
|
||||
|
@ -1119,41 +1085,6 @@ async fn test_channel_link_notifications(
|
|||
cx_c,
|
||||
&[(zed_channel, 0), (vim_channel, 1), (helix_channel, 2)],
|
||||
);
|
||||
|
||||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Members, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
// the members-only channel is still shown for c, but hidden for b
|
||||
assert_channels_list_shape(
|
||||
client_b.channel_store(),
|
||||
cx_b,
|
||||
&[
|
||||
(zed_channel, 0),
|
||||
(active_channel, 1),
|
||||
(vim_channel, 2),
|
||||
(helix_channel, 3),
|
||||
],
|
||||
);
|
||||
cx_b.read(|cx| {
|
||||
client_b.channel_store().read_with(cx, |channel_store, _| {
|
||||
assert_eq!(
|
||||
channel_store
|
||||
.channel_for_id(vim_channel)
|
||||
.unwrap()
|
||||
.visibility,
|
||||
proto::ChannelVisibility::Members
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -1170,24 +1101,20 @@ async fn test_channel_membership_notifications(
|
|||
|
||||
let channels = server
|
||||
.make_channel_tree(
|
||||
&[
|
||||
("zed", None),
|
||||
("active", Some("zed")),
|
||||
("vim", Some("active")),
|
||||
],
|
||||
&[("zed", None), ("vim", Some("zed")), ("opensource", None)],
|
||||
(&client_a, cx_a),
|
||||
)
|
||||
.await;
|
||||
let zed_channel = channels[0];
|
||||
let _active_channel = channels[1];
|
||||
let vim_channel = channels[2];
|
||||
let vim_channel = channels[1];
|
||||
let opensource_channel = channels[2];
|
||||
|
||||
try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
|
||||
[
|
||||
channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
|
||||
channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx),
|
||||
channel_store.invite_member(vim_channel, user_b, proto::ChannelRole::Member, cx),
|
||||
channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Guest, cx),
|
||||
channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Admin, cx),
|
||||
channel_store.invite_member(opensource_channel, user_b, proto::ChannelRole::Member, cx),
|
||||
]
|
||||
}))
|
||||
.await
|
||||
|
@ -1203,14 +1130,6 @@ async fn test_channel_membership_notifications(
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
client_b
|
||||
.channel_store()
|
||||
.update(cx_b, |channel_store, cx| {
|
||||
channel_store.respond_to_channel_invite(vim_channel, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
// we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
|
||||
|
@ -1222,45 +1141,42 @@ async fn test_channel_membership_notifications(
|
|||
depth: 0,
|
||||
id: zed_channel,
|
||||
name: "zed".into(),
|
||||
role: ChannelRole::Guest,
|
||||
},
|
||||
ExpectedChannel {
|
||||
depth: 1,
|
||||
id: vim_channel,
|
||||
name: "vim".into(),
|
||||
role: ChannelRole::Member,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
client_a
|
||||
client_b.channel_store().update(cx_b, |channel_store, _| {
|
||||
channel_store.is_channel_admin(zed_channel)
|
||||
});
|
||||
|
||||
client_b
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.remove_member(vim_channel, user_b, cx)
|
||||
.update(cx_b, |channel_store, cx| {
|
||||
channel_store.respond_to_channel_invite(opensource_channel, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
cx_a.run_until_parked();
|
||||
|
||||
assert_channels(
|
||||
client_b.channel_store(),
|
||||
cx_b,
|
||||
&[
|
||||
ExpectedChannel {
|
||||
depth: 0,
|
||||
id: zed_channel,
|
||||
name: "zed".into(),
|
||||
role: ChannelRole::Guest,
|
||||
},
|
||||
ExpectedChannel {
|
||||
depth: 1,
|
||||
id: vim_channel,
|
||||
name: "vim".into(),
|
||||
role: ChannelRole::Guest,
|
||||
},
|
||||
],
|
||||
)
|
||||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.set_member_role(opensource_channel, user_b, ChannelRole::Admin, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx_a.run_until_parked();
|
||||
|
||||
client_b.channel_store().update(cx_b, |channel_store, _| {
|
||||
channel_store.is_channel_admin(opensource_channel)
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -1329,25 +1245,6 @@ async fn test_guest_access(
|
|||
assert_eq!(participants.len(), 1);
|
||||
assert_eq!(participants[0].id, client_b.user_id().unwrap());
|
||||
});
|
||||
|
||||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Members, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
executor.run_until_parked();
|
||||
|
||||
assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
|
||||
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.join_channel(channel_b, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
assert_channels_list_shape(client_b.channel_store(), cx_b, &[(channel_b, 0)]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -1451,7 +1348,7 @@ async fn test_channel_moving(
|
|||
client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel_store, cx| {
|
||||
channel_store.move_channel(channel_d_id, Some(channel_b_id), cx)
|
||||
channel_store.move_channel(channel_d_id, channel_b_id, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -1476,7 +1373,6 @@ struct ExpectedChannel {
|
|||
depth: usize,
|
||||
id: ChannelId,
|
||||
name: SharedString,
|
||||
role: ChannelRole,
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
@ -1494,7 +1390,6 @@ fn assert_channel_invitations(
|
|||
depth: 0,
|
||||
name: channel.name.clone(),
|
||||
id: channel.id,
|
||||
role: channel.role,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
|
@ -1516,7 +1411,6 @@ fn assert_channels(
|
|||
depth,
|
||||
name: channel.name.clone().into(),
|
||||
id: channel.id,
|
||||
role: channel.role,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
|
|
|
@ -3884,6 +3884,7 @@ async fn test_collaborating_with_diagnostics(
|
|||
|
||||
// Join project as client C and observe the diagnostics.
|
||||
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||
executor.run_until_parked();
|
||||
let project_c_diagnostic_summaries =
|
||||
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
|
||||
project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
|
||||
|
|
|
@ -125,6 +125,7 @@ impl TestServer {
|
|||
let channel_id = server
|
||||
.make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
||||
.await;
|
||||
cx_a.run_until_parked();
|
||||
|
||||
(client_a, client_b, channel_id)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue