Merge branch 'main' into notifications

This commit is contained in:
Max Brunsfeld 2023-10-18 17:20:04 -07:00
commit b07f9fe3b5
61 changed files with 3185 additions and 1148 deletions

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.24.0"
version = "0.25.0"
publish = false
[[bin]]

View file

@ -44,7 +44,7 @@ CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id");
CREATE TABLE "projects" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"room_id" INTEGER REFERENCES rooms (id) NOT NULL,
"room_id" INTEGER REFERENCES rooms (id) ON DELETE CASCADE NOT NULL,
"host_user_id" INTEGER REFERENCES users (id) NOT NULL,
"host_connection_id" INTEGER,
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
@ -192,7 +192,8 @@ CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");
CREATE TABLE "channels" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" VARCHAR NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"visibility" VARCHAR NOT NULL
);
CREATE TABLE IF NOT EXISTS "channel_chat_participants" (
@ -234,6 +235,7 @@ CREATE TABLE "channel_members" (
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"admin" BOOLEAN NOT NULL DEFAULT false,
"role" VARCHAR,
"accepted" BOOLEAN NOT NULL DEFAULT false,
"updated_at" TIMESTAMP NOT NULL DEFAULT now
);

View file

@ -0,0 +1,4 @@
ALTER TABLE channel_members ADD COLUMN role TEXT;
UPDATE channel_members SET role = CASE WHEN admin THEN 'admin' ELSE 'member' END;
ALTER TABLE channels ADD COLUMN visibility TEXT NOT NULL DEFAULT 'members';

View file

@ -0,0 +1,8 @@
-- Add migration script here
ALTER TABLE projects
DROP CONSTRAINT projects_room_id_fkey,
ADD CONSTRAINT projects_room_id_fkey
FOREIGN KEY (room_id)
REFERENCES rooms (id)
ON DELETE CASCADE;

View file

@ -432,6 +432,7 @@ pub struct NewUserResult {
pub struct Channel {
pub id: ChannelId,
pub name: String,
pub visibility: ChannelVisibility,
}
#[derive(Debug, PartialEq)]

View file

@ -1,4 +1,5 @@
use crate::Result;
use rpc::proto;
use sea_orm::{entity::prelude::*, DbErr};
use serde::{Deserialize, Serialize};
@ -82,3 +83,101 @@ id_type!(ChannelBufferCollaboratorId);
id_type!(FlagId);
id_type!(NotificationId);
id_type!(NotificationKindId);
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default)]
#[sea_orm(rs_type = "String", db_type = "String(None)")]
pub enum ChannelRole {
#[sea_orm(string_value = "admin")]
Admin,
#[sea_orm(string_value = "member")]
#[default]
Member,
#[sea_orm(string_value = "guest")]
Guest,
#[sea_orm(string_value = "banned")]
Banned,
}
impl ChannelRole {
pub fn should_override(&self, other: Self) -> bool {
use ChannelRole::*;
match self {
Admin => matches!(other, Member | Banned | Guest),
Member => matches!(other, Banned | Guest),
Banned => matches!(other, Guest),
Guest => false,
}
}
pub fn max(&self, other: Self) -> Self {
if self.should_override(other) {
*self
} else {
other
}
}
}
impl From<proto::ChannelRole> for ChannelRole {
fn from(value: proto::ChannelRole) -> Self {
match value {
proto::ChannelRole::Admin => ChannelRole::Admin,
proto::ChannelRole::Member => ChannelRole::Member,
proto::ChannelRole::Guest => ChannelRole::Guest,
proto::ChannelRole::Banned => ChannelRole::Banned,
}
}
}
impl Into<proto::ChannelRole> for ChannelRole {
fn into(self) -> proto::ChannelRole {
match self {
ChannelRole::Admin => proto::ChannelRole::Admin,
ChannelRole::Member => proto::ChannelRole::Member,
ChannelRole::Guest => proto::ChannelRole::Guest,
ChannelRole::Banned => proto::ChannelRole::Banned,
}
}
}
impl Into<i32> for ChannelRole {
fn into(self) -> i32 {
let proto: proto::ChannelRole = self.into();
proto.into()
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]
#[sea_orm(rs_type = "String", db_type = "String(None)")]
pub enum ChannelVisibility {
#[sea_orm(string_value = "public")]
Public,
#[sea_orm(string_value = "members")]
#[default]
Members,
}
impl From<proto::ChannelVisibility> for ChannelVisibility {
fn from(value: proto::ChannelVisibility) -> Self {
match value {
proto::ChannelVisibility::Public => ChannelVisibility::Public,
proto::ChannelVisibility::Members => ChannelVisibility::Members,
}
}
}
impl Into<proto::ChannelVisibility> for ChannelVisibility {
fn into(self) -> proto::ChannelVisibility {
match self {
ChannelVisibility::Public => proto::ChannelVisibility::Public,
ChannelVisibility::Members => proto::ChannelVisibility::Members,
}
}
}
impl Into<i32> for ChannelVisibility {
fn into(self) -> i32 {
let proto: proto::ChannelVisibility = self.into();
proto.into()
}
}

View file

@ -482,7 +482,9 @@ impl Database {
)
.await?;
channel_members = self.get_channel_members_internal(channel_id, &*tx).await?;
channel_members = self
.get_channel_participants_internal(channel_id, &*tx)
.await?;
let collaborators = self
.get_channel_buffer_collaborators_internal(channel_id, &*tx)
.await?;

File diff suppressed because it is too large Load diff

View file

@ -233,7 +233,9 @@ impl Database {
self.observe_channel_message_internal(channel_id, user_id, message_id, &*tx)
.await?;
let mut channel_members = self.get_channel_members_internal(channel_id, &*tx).await?;
let mut channel_members = self
.get_channel_participants_internal(channel_id, &*tx)
.await?;
channel_members.retain(|member| !participant_user_ids.contains(member));
Ok((message_id, participant_connection_ids, channel_members))
@ -386,8 +388,22 @@ impl Database {
.filter(channel_message::Column::SenderId.eq(user_id))
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("no such message"))?;
if self
.check_user_is_channel_admin(channel_id, user_id, &*tx)
.await
.is_ok()
{
let result = channel_message::Entity::delete_by_id(message_id)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("no such message"))?;
}
} else {
Err(anyhow!("operation could not be completed"))?;
}
}
Ok(participant_connection_ids)

View file

@ -53,7 +53,9 @@ impl Database {
let (channel_id, room) = self.get_channel_room(room_id, &tx).await?;
let channel_members;
if let Some(channel_id) = channel_id {
channel_members = self.get_channel_members_internal(channel_id, &tx).await?;
channel_members = self
.get_channel_participants_internal(channel_id, &tx)
.await?;
} else {
channel_members = Vec::new();
@ -298,98 +300,139 @@ impl Database {
}
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryParticipantIndices {
ParticipantIndex,
if channel_id.is_some() {
Err(anyhow!("tried to join channel call directly"))?
}
let existing_participant_indices: Vec<i32> = room_participant::Entity::find()
.filter(
room_participant::Column::RoomId
.eq(room_id)
.and(room_participant::Column::ParticipantIndex.is_not_null()),
)
.select_only()
.column(room_participant::Column::ParticipantIndex)
.into_values::<_, QueryParticipantIndices>()
.all(&*tx)
let participant_index = self
.get_next_participant_index_internal(room_id, &*tx)
.await?;
let mut participant_index = 0;
while existing_participant_indices.contains(&participant_index) {
participant_index += 1;
}
if let Some(channel_id) = channel_id {
self.check_user_is_channel_member(channel_id, user_id, &*tx)
.await?;
room_participant::Entity::insert_many([room_participant::ActiveModel {
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(user_id),
let result = room_participant::Entity::update_many()
.filter(
Condition::all()
.add(room_participant::Column::RoomId.eq(room_id))
.add(room_participant::Column::UserId.eq(user_id))
.add(room_participant::Column::AnsweringConnectionId.is_null()),
)
.set(room_participant::ActiveModel {
participant_index: ActiveValue::Set(Some(participant_index)),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
answering_connection_lost: ActiveValue::set(false),
calling_user_id: ActiveValue::set(user_id),
calling_connection_id: ActiveValue::set(connection.id as i32),
calling_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
participant_index: ActiveValue::Set(Some(participant_index)),
..Default::default()
}])
.on_conflict(
OnConflict::columns([room_participant::Column::UserId])
.update_columns([
room_participant::Column::AnsweringConnectionId,
room_participant::Column::AnsweringConnectionServerId,
room_participant::Column::AnsweringConnectionLost,
room_participant::Column::ParticipantIndex,
])
.to_owned(),
)
})
.exec(&*tx)
.await?;
} else {
let result = room_participant::Entity::update_many()
.filter(
Condition::all()
.add(room_participant::Column::RoomId.eq(room_id))
.add(room_participant::Column::UserId.eq(user_id))
.add(room_participant::Column::AnsweringConnectionId.is_null()),
)
.set(room_participant::ActiveModel {
participant_index: ActiveValue::Set(Some(participant_index)),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
answering_connection_lost: ActiveValue::set(false),
..Default::default()
})
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("room does not exist or was already joined"))?;
}
if result.rows_affected == 0 {
Err(anyhow!("room does not exist or was already joined"))?;
}
let room = self.get_room(room_id, &tx).await?;
let channel_members = if let Some(channel_id) = channel_id {
self.get_channel_members_internal(channel_id, &tx).await?
} else {
Vec::new()
};
Ok(JoinRoom {
room,
channel_id,
channel_members,
channel_id: None,
channel_members: vec![],
})
})
.await
}
async fn get_next_participant_index_internal(
&self,
room_id: RoomId,
tx: &DatabaseTransaction,
) -> Result<i32> {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryParticipantIndices {
ParticipantIndex,
}
let existing_participant_indices: Vec<i32> = room_participant::Entity::find()
.filter(
room_participant::Column::RoomId
.eq(room_id)
.and(room_participant::Column::ParticipantIndex.is_not_null()),
)
.select_only()
.column(room_participant::Column::ParticipantIndex)
.into_values::<_, QueryParticipantIndices>()
.all(&*tx)
.await?;
let mut participant_index = 0;
while existing_participant_indices.contains(&participant_index) {
participant_index += 1;
}
Ok(participant_index)
}
pub async fn channel_id_for_room(&self, room_id: RoomId) -> Result<Option<ChannelId>> {
self.transaction(|tx| async move {
let room: Option<room::Model> = room::Entity::find()
.filter(room::Column::Id.eq(room_id))
.one(&*tx)
.await?;
Ok(room.and_then(|room| room.channel_id))
})
.await
}
pub(crate) async fn join_channel_room_internal(
&self,
channel_id: ChannelId,
room_id: RoomId,
user_id: UserId,
connection: ConnectionId,
tx: &DatabaseTransaction,
) -> Result<JoinRoom> {
let participant_index = self
.get_next_participant_index_internal(room_id, &*tx)
.await?;
room_participant::Entity::insert_many([room_participant::ActiveModel {
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(user_id),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
answering_connection_lost: ActiveValue::set(false),
calling_user_id: ActiveValue::set(user_id),
calling_connection_id: ActiveValue::set(connection.id as i32),
calling_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
participant_index: ActiveValue::Set(Some(participant_index)),
..Default::default()
}])
.on_conflict(
OnConflict::columns([room_participant::Column::UserId])
.update_columns([
room_participant::Column::AnsweringConnectionId,
room_participant::Column::AnsweringConnectionServerId,
room_participant::Column::AnsweringConnectionLost,
room_participant::Column::ParticipantIndex,
])
.to_owned(),
)
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
let channel_members = self
.get_channel_participants_internal(channel_id, &tx)
.await?;
Ok(JoinRoom {
room,
channel_id: Some(channel_id),
channel_members,
})
}
pub async fn rejoin_room(
&self,
rejoin_room: proto::RejoinRoom,
@ -681,7 +724,8 @@ impl Database {
let (channel_id, room) = self.get_channel_room(room_id, &tx).await?;
let channel_members = if let Some(channel_id) = channel_id {
self.get_channel_members_internal(channel_id, &tx).await?
self.get_channel_participants_internal(channel_id, &tx)
.await?
} else {
Vec::new()
};
@ -839,7 +883,8 @@ impl Database {
};
let channel_members = if let Some(channel_id) = channel_id {
self.get_channel_members_internal(channel_id, &tx).await?
self.get_channel_participants_internal(channel_id, &tx)
.await?
} else {
Vec::new()
};

View file

@ -1,4 +1,4 @@
use crate::db::ChannelId;
use crate::db::{ChannelId, ChannelVisibility};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
@ -7,6 +7,7 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: ChannelId,
pub name: String,
pub visibility: ChannelVisibility,
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -1,7 +1,7 @@
use crate::db::{channel_member, ChannelId, ChannelMemberId, UserId};
use crate::db::{channel_member, ChannelId, ChannelMemberId, ChannelRole, UserId};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "channel_members")]
pub struct Model {
#[sea_orm(primary_key)]
@ -9,7 +9,7 @@ pub struct Model {
pub channel_id: ChannelId,
pub user_id: UserId,
pub accepted: bool,
pub admin: bool,
pub role: ChannelRole,
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -161,6 +161,7 @@ fn graph(channels: &[(ChannelId, &'static str)], edges: &[(ChannelId, ChannelId)
graph.channels.push(Channel {
id: *id,
name: name.to_string(),
visibility: ChannelVisibility::Members,
})
}

View file

@ -53,7 +53,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
db.invite_channel_member(zed_id, b_id, a_id, false)
db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member)
.await
.unwrap();
@ -206,7 +206,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
.await
.unwrap();
db.invite_channel_member(channel, observer_id, user_id, false)
db.invite_channel_member(channel, observer_id, user_id, ChannelRole::Member)
.await
.unwrap();
db.respond_to_channel_invite(channel, observer_id, true)

View file

@ -8,11 +8,14 @@ use crate::{
db::{
queries::channels::ChannelGraph,
tests::{graph, TEST_RELEASE_CHANNEL},
ChannelId, Database, NewUserParams,
ChannelId, ChannelRole, Database, NewUserParams, RoomId, UserId,
},
test_both_dbs,
};
use std::sync::Arc;
use std::sync::{
atomic::{AtomicI32, Ordering},
Arc,
};
test_both_dbs!(test_channels, test_channels_postgres, test_channels_sqlite);
@ -46,9 +49,9 @@ async fn test_channels(db: &Arc<Database>) {
let zed_id = db.create_root_channel("zed", a_id).await.unwrap();
// Make sure that people cannot read channels they haven't been invited to
assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none());
assert!(db.get_channel(zed_id, b_id).await.is_err());
db.invite_channel_member(zed_id, b_id, a_id, false)
db.invite_channel_member(zed_id, b_id, a_id, ChannelRole::Member)
.await
.unwrap();
@ -123,9 +126,13 @@ async fn test_channels(db: &Arc<Database>) {
);
// Update member permissions
let set_subchannel_admin = db.set_channel_member_admin(crdb_id, a_id, b_id, true).await;
let set_subchannel_admin = db
.set_channel_member_role(crdb_id, a_id, b_id, ChannelRole::Admin)
.await;
assert!(set_subchannel_admin.is_err());
let set_channel_admin = db.set_channel_member_admin(zed_id, a_id, b_id, true).await;
let set_channel_admin = db
.set_channel_member_role(zed_id, a_id, b_id, ChannelRole::Admin)
.await;
assert!(set_channel_admin.is_ok());
let result = db.get_channels_for_user(b_id).await.unwrap();
@ -148,7 +155,7 @@ async fn test_channels(db: &Arc<Database>) {
// Remove a single channel
db.delete_channel(crdb_id, a_id).await.unwrap();
assert!(db.get_channel(crdb_id, a_id).await.unwrap().is_none());
assert!(db.get_channel(crdb_id, a_id).await.is_err());
// Remove a channel tree
let (mut channel_ids, user_ids) = db.delete_channel(rust_id, a_id).await.unwrap();
@ -156,9 +163,9 @@ async fn test_channels(db: &Arc<Database>) {
assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
assert_eq!(user_ids, &[a_id]);
assert!(db.get_channel(rust_id, a_id).await.unwrap().is_none());
assert!(db.get_channel(cargo_id, a_id).await.unwrap().is_none());
assert!(db.get_channel(cargo_ra_id, a_id).await.unwrap().is_none());
assert!(db.get_channel(rust_id, a_id).await.is_err());
assert!(db.get_channel(cargo_id, a_id).await.is_err());
assert!(db.get_channel(cargo_ra_id, a_id).await.is_err());
}
test_both_dbs!(
@ -196,15 +203,11 @@ async fn test_joining_channels(db: &Arc<Database>) {
.user_id;
let channel_1 = db.create_root_channel("channel_1", user_1).await.unwrap();
let room_1 = db
.get_or_create_channel_room(channel_1, "1", TEST_RELEASE_CHANNEL)
.await
.unwrap();
// can join a room with membership to its channel
let joined_room = db
.join_room(
room_1,
let (joined_room, _) = db
.join_channel(
channel_1,
user_1,
ConnectionId { owner_id, id: 1 },
TEST_RELEASE_CHANNEL,
@ -213,11 +216,12 @@ async fn test_joining_channels(db: &Arc<Database>) {
.unwrap();
assert_eq!(joined_room.room.participants.len(), 1);
let room_id = RoomId::from_proto(joined_room.room.id);
drop(joined_room);
// cannot join a room without membership to its channel
assert!(db
.join_room(
room_1,
room_id,
user_2,
ConnectionId { owner_id, id: 1 },
TEST_RELEASE_CHANNEL
@ -235,55 +239,21 @@ test_both_dbs!(
async fn test_channel_invites(db: &Arc<Database>) {
db.create_server("test").await.unwrap();
let user_1 = db
.create_user(
"user1@example.com",
false,
NewUserParams {
github_login: "user1".into(),
github_user_id: 5,
},
)
.await
.unwrap()
.user_id;
let user_2 = db
.create_user(
"user2@example.com",
false,
NewUserParams {
github_login: "user2".into(),
github_user_id: 6,
},
)
.await
.unwrap()
.user_id;
let user_3 = db
.create_user(
"user3@example.com",
false,
NewUserParams {
github_login: "user3".into(),
github_user_id: 7,
},
)
.await
.unwrap()
.user_id;
let user_1 = new_test_user(db, "user1@example.com").await;
let user_2 = new_test_user(db, "user2@example.com").await;
let user_3 = new_test_user(db, "user3@example.com").await;
let channel_1_1 = db.create_root_channel("channel_1", user_1).await.unwrap();
let channel_1_2 = db.create_root_channel("channel_2", user_1).await.unwrap();
db.invite_channel_member(channel_1_1, user_2, user_1, false)
db.invite_channel_member(channel_1_1, user_2, user_1, ChannelRole::Member)
.await
.unwrap();
db.invite_channel_member(channel_1_2, user_2, user_1, false)
db.invite_channel_member(channel_1_2, user_2, user_1, ChannelRole::Member)
.await
.unwrap();
db.invite_channel_member(channel_1_1, user_3, user_1, true)
db.invite_channel_member(channel_1_1, user_3, user_1, ChannelRole::Admin)
.await
.unwrap();
@ -307,27 +277,29 @@ async fn test_channel_invites(db: &Arc<Database>) {
assert_eq!(user_3_invites, &[channel_1_1]);
let members = db
.get_channel_member_details(channel_1_1, user_1)
let mut members = db
.get_channel_participant_details(channel_1_1, user_1)
.await
.unwrap();
members.sort_by_key(|member| member.user_id);
assert_eq!(
members,
&[
proto::ChannelMember {
user_id: user_1.to_proto(),
kind: proto::channel_member::Kind::Member.into(),
admin: true,
role: proto::ChannelRole::Admin.into(),
},
proto::ChannelMember {
user_id: user_2.to_proto(),
kind: proto::channel_member::Kind::Invitee.into(),
admin: false,
role: proto::ChannelRole::Member.into(),
},
proto::ChannelMember {
user_id: user_3.to_proto(),
kind: proto::channel_member::Kind::Invitee.into(),
admin: true,
role: proto::ChannelRole::Admin.into(),
},
]
);
@ -342,7 +314,7 @@ async fn test_channel_invites(db: &Arc<Database>) {
.unwrap();
let members = db
.get_channel_member_details(channel_1_3, user_1)
.get_channel_participant_details(channel_1_3, user_1)
.await
.unwrap();
assert_eq!(
@ -351,12 +323,12 @@ async fn test_channel_invites(db: &Arc<Database>) {
proto::ChannelMember {
user_id: user_1.to_proto(),
kind: proto::channel_member::Kind::Member.into(),
admin: true,
role: proto::ChannelRole::Admin.into(),
},
proto::ChannelMember {
user_id: user_2.to_proto(),
kind: proto::channel_member::Kind::AncestorMember.into(),
admin: false,
role: proto::ChannelRole::Member.into(),
},
]
);
@ -405,11 +377,7 @@ async fn test_channel_renames(db: &Arc<Database>) {
let zed_archive_id = zed_id;
let (channel, _) = db
.get_channel(zed_archive_id, user_1)
.await
.unwrap()
.unwrap();
let channel = db.get_channel(zed_archive_id, user_1).await.unwrap();
assert_eq!(channel.name, "zed-archive");
let non_permissioned_rename = db
@ -835,6 +803,284 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
);
}
test_both_dbs!(
test_user_is_channel_participant,
test_user_is_channel_participant_postgres,
test_user_is_channel_participant_sqlite
);
async fn test_user_is_channel_participant(db: &Arc<Database>) {
let admin = new_test_user(db, "admin@example.com").await;
let member = new_test_user(db, "member@example.com").await;
let guest = new_test_user(db, "guest@example.com").await;
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
let active_channel = db
.create_channel("active", Some(zed_channel), admin)
.await
.unwrap();
let vim_channel = db
.create_channel("vim", Some(active_channel), admin)
.await
.unwrap();
db.set_channel_visibility(vim_channel, crate::db::ChannelVisibility::Public, admin)
.await
.unwrap();
db.invite_channel_member(active_channel, member, admin, ChannelRole::Member)
.await
.unwrap();
db.invite_channel_member(vim_channel, guest, admin, ChannelRole::Guest)
.await
.unwrap();
db.respond_to_channel_invite(active_channel, member, true)
.await
.unwrap();
db.transaction(|tx| async move {
db.check_user_is_channel_participant(vim_channel, admin, &*tx)
.await
})
.await
.unwrap();
db.transaction(|tx| async move {
db.check_user_is_channel_participant(vim_channel, member, &*tx)
.await
})
.await
.unwrap();
let mut members = db
.get_channel_participant_details(vim_channel, admin)
.await
.unwrap();
members.sort_by_key(|member| member.user_id);
assert_eq!(
members,
&[
proto::ChannelMember {
user_id: admin.to_proto(),
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(),
role: proto::ChannelRole::Member.into(),
},
proto::ChannelMember {
user_id: guest.to_proto(),
kind: proto::channel_member::Kind::Invitee.into(),
role: proto::ChannelRole::Guest.into(),
},
]
);
db.respond_to_channel_invite(vim_channel, guest, true)
.await
.unwrap();
db.transaction(|tx| async move {
db.check_user_is_channel_participant(vim_channel, guest, &*tx)
.await
})
.await
.unwrap();
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
assert_dag(channels, &[(vim_channel, None)]);
let channels = db.get_channels_for_user(member).await.unwrap().channels;
assert_dag(
channels,
&[(active_channel, None), (vim_channel, Some(active_channel))],
);
db.set_channel_member_role(vim_channel, admin, guest, ChannelRole::Banned)
.await
.unwrap();
assert!(db
.transaction(|tx| async move {
db.check_user_is_channel_participant(vim_channel, guest, &*tx)
.await
})
.await
.is_err());
let mut members = db
.get_channel_participant_details(vim_channel, admin)
.await
.unwrap();
members.sort_by_key(|member| member.user_id);
assert_eq!(
members,
&[
proto::ChannelMember {
user_id: admin.to_proto(),
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(),
role: proto::ChannelRole::Member.into(),
},
proto::ChannelMember {
user_id: guest.to_proto(),
kind: proto::channel_member::Kind::Member.into(),
role: proto::ChannelRole::Banned.into(),
},
]
);
db.remove_channel_member(vim_channel, guest, admin)
.await
.unwrap();
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
.await
.unwrap();
db.invite_channel_member(zed_channel, guest, admin, ChannelRole::Guest)
.await
.unwrap();
// currently people invited to parent channels are not shown here
let mut members = db
.get_channel_participant_details(vim_channel, admin)
.await
.unwrap();
members.sort_by_key(|member| member.user_id);
assert_eq!(
members,
&[
proto::ChannelMember {
user_id: admin.to_proto(),
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(),
role: proto::ChannelRole::Member.into(),
},
]
);
db.respond_to_channel_invite(zed_channel, guest, true)
.await
.unwrap();
db.transaction(|tx| async move {
db.check_user_is_channel_participant(zed_channel, guest, &*tx)
.await
})
.await
.unwrap();
assert!(db
.transaction(|tx| async move {
db.check_user_is_channel_participant(active_channel, guest, &*tx)
.await
})
.await
.is_err(),);
db.transaction(|tx| async move {
db.check_user_is_channel_participant(vim_channel, guest, &*tx)
.await
})
.await
.unwrap();
let mut members = db
.get_channel_participant_details(vim_channel, admin)
.await
.unwrap();
members.sort_by_key(|member| member.user_id);
assert_eq!(
members,
&[
proto::ChannelMember {
user_id: admin.to_proto(),
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(),
role: proto::ChannelRole::Member.into(),
},
proto::ChannelMember {
user_id: guest.to_proto(),
kind: proto::channel_member::Kind::AncestorMember.into(),
role: proto::ChannelRole::Guest.into(),
},
]
);
let channels = db.get_channels_for_user(guest).await.unwrap().channels;
assert_dag(
channels,
&[(zed_channel, None), (vim_channel, Some(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_channel("active", Some(zed_channel), admin)
.await
.unwrap();
let vim_channel = db
.create_channel("vim", Some(active_channel), admin)
.await
.unwrap();
let vim2_channel = db
.create_channel("vim2", Some(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 { db.most_public_ancestor_for_channel(vim_channel, &*tx).await },
)
.await
.unwrap();
assert_eq!(most_public, Some(zed_channel))
}
#[track_caller]
fn assert_dag(actual: ChannelGraph, expected: &[(ChannelId, Option<ChannelId>)]) {
let mut actual_map: HashMap<ChannelId, HashSet<ChannelId>> = HashMap::default();
@ -859,3 +1105,19 @@ fn assert_dag(actual: ChannelGraph, expected: &[(ChannelId, Option<ChannelId>)])
pretty_assertions::assert_eq!(actual_map, expected_map)
}
static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5);
async fn new_test_user(db: &Arc<Database>, email: &str) -> UserId {
db.create_user(
email,
false,
NewUserParams {
github_login: email[0..email.find("@").unwrap()].to_string(),
github_user_id: GITHUB_USER_ID.fetch_add(1, Ordering::SeqCst),
},
)
.await
.unwrap()
.user_id
}

View file

@ -1,5 +1,5 @@
use crate::{
db::{Database, MessageId, NewUserParams},
db::{ChannelRole, Database, MessageId, NewUserParams},
test_both_dbs,
};
use channel::mentions_to_proto;
@ -158,12 +158,13 @@ async fn test_unseen_channel_messages(db: &Arc<Database>) {
let channel_1 = db.create_channel("channel", None, user).await.unwrap();
let channel_2 = db.create_channel("channel-2", None, user).await.unwrap();
db.invite_channel_member(channel_1, observer, user, false)
db.invite_channel_member(channel_1, observer, user, ChannelRole::Member)
.await
.unwrap();
db.invite_channel_member(channel_2, observer, user, false)
db.invite_channel_member(channel_2, observer, user, ChannelRole::Member)
.await
.unwrap();
db.respond_to_channel_invite(channel_1, observer, true)
.await
.unwrap();
@ -341,7 +342,7 @@ async fn test_channel_message_mentions(db: &Arc<Database>) {
.user_id;
let channel = db.create_channel("channel", None, user_a).await.unwrap();
db.invite_channel_member(channel, user_b, user_a, false)
db.invite_channel_member(channel, user_b, user_a, ChannelRole::Member)
.await
.unwrap();
db.respond_to_channel_invite(channel, user_b, true)

View file

@ -3,8 +3,8 @@ mod connection_pool;
use crate::{
auth,
db::{
self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId,
ServerId, User, UserId,
self, BufferId, ChannelId, ChannelVisibility, ChannelsForUser, Database, MessageId,
ProjectId, RoomId, ServerId, User, UserId,
},
executor::Executor,
AppState, Result,
@ -256,7 +256,8 @@ impl Server {
.add_request_handler(delete_channel)
.add_request_handler(invite_channel_member)
.add_request_handler(remove_channel_member)
.add_request_handler(set_channel_member_admin)
.add_request_handler(set_channel_member_role)
.add_request_handler(set_channel_visibility)
.add_request_handler(rename_channel)
.add_request_handler(join_channel_buffer)
.add_request_handler(leave_channel_buffer)
@ -979,6 +980,13 @@ async fn join_room(
session: Session,
) -> Result<()> {
let room_id = RoomId::from_proto(request.id);
let channel_id = session.db().await.channel_id_for_room(room_id).await?;
if let Some(channel_id) = channel_id {
return join_channel_internal(channel_id, Box::new(response), session).await;
}
let joined_room = {
let room = session
.db()
@ -994,16 +1002,6 @@ async fn join_room(
room.into_inner()
};
if let Some(channel_id) = joined_room.channel_id {
channel_updated(
channel_id,
&joined_room.room,
&joined_room.channel_members,
&session.peer,
&*session.connection_pool().await,
)
}
for connection_id in session
.connection_pool()
.await
@ -1041,7 +1039,7 @@ async fn join_room(
response.send(proto::JoinRoomResponse {
room: Some(joined_room.room),
channel_id: joined_room.channel_id.map(|id| id.to_proto()),
channel_id: None,
live_kit_connection_info,
})?;
@ -2224,6 +2222,7 @@ async fn create_channel(
let channel = proto::Channel {
id: id.to_proto(),
name: request.name,
visibility: proto::ChannelVisibility::Members as i32,
};
response.send(proto::CreateChannelResponse {
@ -2297,17 +2296,20 @@ async fn invite_channel_member(
let channel_id = ChannelId::from_proto(request.channel_id);
let invitee_id = UserId::from_proto(request.user_id);
let notifications = db
.invite_channel_member(channel_id, invitee_id, session.user_id, request.admin)
.invite_channel_member(
channel_id,
invitee_id,
session.user_id,
request.role().into(),
)
.await?;
let (channel, _) = db
.get_channel(channel_id, session.user_id)
.await?
.ok_or_else(|| anyhow!("channel not found"))?;
let channel = db.get_channel(channel_id, session.user_id).await?;
let mut update = proto::UpdateChannels::default();
update.channel_invitations.push(proto::Channel {
id: channel.id.to_proto(),
visibility: channel.visibility.into(),
name: channel.name,
});
@ -2361,27 +2363,63 @@ async fn remove_channel_member(
Ok(())
}
async fn set_channel_member_admin(
request: proto::SetChannelMemberAdmin,
response: Response<proto::SetChannelMemberAdmin>,
async fn set_channel_visibility(
request: proto::SetChannelVisibility,
response: Response<proto::SetChannelVisibility>,
session: Session,
) -> Result<()> {
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
let visibility = request.visibility().into();
let channel = db
.set_channel_visibility(channel_id, visibility, session.user_id)
.await?;
let mut update = proto::UpdateChannels::default();
update.channels.push(proto::Channel {
id: channel.id.to_proto(),
name: channel.name,
visibility: channel.visibility.into(),
});
let member_ids = db.get_channel_members(channel_id).await?;
let connection_pool = session.connection_pool().await;
for member_id in member_ids {
for connection_id in connection_pool.user_connection_ids(member_id) {
session.peer.send(connection_id, update.clone())?;
}
}
response.send(proto::Ack {})?;
Ok(())
}
async fn set_channel_member_role(
request: proto::SetChannelMemberRole,
response: Response<proto::SetChannelMemberRole>,
session: Session,
) -> Result<()> {
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
let member_id = UserId::from_proto(request.user_id);
db.set_channel_member_admin(channel_id, session.user_id, member_id, request.admin)
let channel_member = db
.set_channel_member_role(
channel_id,
session.user_id,
member_id,
request.role().into(),
)
.await?;
let (channel, has_accepted) = db
.get_channel(channel_id, member_id)
.await?
.ok_or_else(|| anyhow!("channel not found"))?;
let channel = db.get_channel(channel_id, session.user_id).await?;
let mut update = proto::UpdateChannels::default();
if has_accepted {
if channel_member.accepted {
update.channel_permissions.push(proto::ChannelPermission {
channel_id: channel.id.to_proto(),
is_admin: request.admin,
role: request.role,
});
}
@ -2404,13 +2442,14 @@ async fn rename_channel(
) -> Result<()> {
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
let new_name = db
let channel = db
.rename_channel(channel_id, session.user_id, &request.name)
.await?;
let channel = proto::Channel {
id: request.channel_id,
name: new_name,
id: channel.id.to_proto(),
name: channel.name,
visibility: channel.visibility.into(),
};
response.send(proto::RenameChannelResponse {
channel: Some(channel.clone()),
@ -2448,6 +2487,7 @@ async fn link_channel(
.into_iter()
.map(|channel| proto::Channel {
id: channel.id.to_proto(),
visibility: channel.visibility.into(),
name: channel.name,
})
.collect(),
@ -2539,6 +2579,7 @@ async fn move_channel(
.into_iter()
.map(|channel| proto::Channel {
id: channel.id.to_proto(),
visibility: channel.visibility.into(),
name: channel.name,
})
.collect(),
@ -2564,7 +2605,7 @@ async fn get_channel_members(
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
let members = db
.get_channel_member_details(channel_id, session.user_id)
.get_channel_participant_details(channel_id, session.user_id)
.await?;
response.send(proto::GetChannelMembersResponse { members })?;
Ok(())
@ -2581,51 +2622,16 @@ async fn respond_to_channel_invite(
.respond_to_channel_invite(channel_id, session.user_id, request.accept)
.await?;
let mut update = proto::UpdateChannels::default();
update
.remove_channel_invitations
.push(channel_id.to_proto());
if request.accept {
let result = db.get_channel_for_user(channel_id, session.user_id).await?;
channel_membership_updated(db, channel_id, &session).await?;
} else {
let mut update = proto::UpdateChannels::default();
update
.channels
.extend(
result
.channels
.channels
.into_iter()
.map(|channel| proto::Channel {
id: channel.id.to_proto(),
name: channel.name,
}),
);
update.unseen_channel_messages = result.channel_messages;
update.unseen_channel_buffer_changes = result.unseen_buffer_changes;
update.insert_edge = result.channels.edges;
update
.channel_participants
.extend(
result
.channel_participants
.into_iter()
.map(|(channel_id, user_ids)| proto::ChannelParticipants {
channel_id: channel_id.to_proto(),
participant_user_ids: user_ids.into_iter().map(UserId::to_proto).collect(),
}),
);
update
.channel_permissions
.extend(
result
.channels_with_admin_privileges
.into_iter()
.map(|channel_id| proto::ChannelPermission {
channel_id: channel_id.to_proto(),
is_admin: true,
}),
);
.remove_channel_invitations
.push(channel_id.to_proto());
session.peer.send(session.connection_id, update)?;
}
session.peer.send(session.connection_id, update)?;
send_notifications(
&*session.connection_pool().await,
&session.peer,
@ -2636,25 +2642,92 @@ async fn respond_to_channel_invite(
Ok(())
}
async fn channel_membership_updated(
db: tokio::sync::MutexGuard<'_, DbHandle>,
channel_id: ChannelId,
session: &Session,
) -> Result<(), crate::Error> {
let mut update = proto::UpdateChannels::default();
update
.remove_channel_invitations
.push(channel_id.to_proto());
let result = db.get_channel_for_user(channel_id, session.user_id).await?;
update.channels.extend(
result
.channels
.channels
.into_iter()
.map(|channel| proto::Channel {
id: channel.id.to_proto(),
visibility: channel.visibility.into(),
name: channel.name,
}),
);
update.unseen_channel_messages = result.channel_messages;
update.unseen_channel_buffer_changes = result.unseen_buffer_changes;
update.insert_edge = result.channels.edges;
update
.channel_participants
.extend(
result
.channel_participants
.into_iter()
.map(|(channel_id, user_ids)| proto::ChannelParticipants {
channel_id: channel_id.to_proto(),
participant_user_ids: user_ids.into_iter().map(UserId::to_proto).collect(),
}),
);
update
.channel_permissions
.extend(
result
.channels_with_admin_privileges
.into_iter()
.map(|channel_id| proto::ChannelPermission {
channel_id: channel_id.to_proto(),
role: proto::ChannelRole::Admin.into(),
}),
);
session.peer.send(session.connection_id, update)?;
Ok(())
}
async fn join_channel(
request: proto::JoinChannel,
response: Response<proto::JoinChannel>,
session: Session,
) -> Result<()> {
let channel_id = ChannelId::from_proto(request.channel_id);
let live_kit_room = format!("channel-{}", nanoid::nanoid!(30));
join_channel_internal(channel_id, Box::new(response), session).await
}
trait JoinChannelInternalResponse {
fn send(self, result: proto::JoinRoomResponse) -> Result<()>;
}
impl JoinChannelInternalResponse for Response<proto::JoinChannel> {
fn send(self, result: proto::JoinRoomResponse) -> Result<()> {
Response::<proto::JoinChannel>::send(self, result)
}
}
impl JoinChannelInternalResponse for Response<proto::JoinRoom> {
fn send(self, result: proto::JoinRoomResponse) -> Result<()> {
Response::<proto::JoinRoom>::send(self, result)
}
}
async fn join_channel_internal(
channel_id: ChannelId,
response: Box<impl JoinChannelInternalResponse>,
session: Session,
) -> Result<()> {
let joined_room = {
leave_room_for_session(&session).await?;
let db = session.db().await;
let room_id = db
.get_or_create_channel_room(channel_id, &live_kit_room, &*RELEASE_CHANNEL_NAME)
.await?;
let joined_room = db
.join_room(
room_id,
let (joined_room, joined_channel) = db
.join_channel(
channel_id,
session.user_id,
session.connection_id,
RELEASE_CHANNEL_NAME.as_str(),
@ -2681,9 +2754,13 @@ async fn join_channel(
live_kit_connection_info,
})?;
if let Some(joined_channel) = joined_channel {
channel_membership_updated(db, joined_channel, &session).await?
}
room_updated(&joined_room.room, &session.peer);
joined_room.into_inner()
joined_room
};
channel_updated(
@ -2695,7 +2772,6 @@ async fn join_channel(
);
update_user_contacts(session.user_id, &session).await?;
Ok(())
}
@ -3152,6 +3228,7 @@ fn build_initial_channels_update(
update.channels.push(proto::Channel {
id: channel.id.to_proto(),
name: channel.name,
visibility: channel.visibility.into(),
});
}
@ -3176,7 +3253,7 @@ fn build_initial_channels_update(
.into_iter()
.map(|id| proto::ChannelPermission {
channel_id: id.to_proto(),
is_admin: true,
role: proto::ChannelRole::Admin.into(),
}),
);
@ -3184,6 +3261,8 @@ fn build_initial_channels_update(
update.channel_invitations.push(proto::Channel {
id: channel.id.to_proto(),
name: channel.name,
// TODO: Visibility
visibility: ChannelVisibility::Public.into(),
});
}

View file

@ -11,7 +11,10 @@ use collections::HashMap;
use editor::{Anchor, Editor, ToOffset};
use futures::future;
use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
use rpc::{
proto::{self, PeerId},
RECEIVE_TIMEOUT,
};
use serde_json::json;
use std::{ops::Range, sync::Arc};
@ -445,6 +448,7 @@ fn channel(id: u64, name: &'static str) -> Channel {
Channel {
id,
name: name.to_string(),
visibility: proto::ChannelVisibility::Members,
unseen_note_version: None,
unseen_message_id: None,
}

View file

@ -6,7 +6,10 @@ use call::ActiveCall;
use channel::{ChannelId, ChannelMembership, ChannelStore};
use client::User;
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
use rpc::{proto, RECEIVE_TIMEOUT};
use rpc::{
proto::{self, ChannelRole},
RECEIVE_TIMEOUT,
};
use std::sync::Arc;
#[gpui::test]
@ -68,7 +71,12 @@ async fn test_core_channels(
.update(cx_a, |store, cx| {
assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
let invite = store.invite_member(channel_a_id, client_b.user_id().unwrap(), false, cx);
let invite = store.invite_member(
channel_a_id,
client_b.user_id().unwrap(),
proto::ChannelRole::Member,
cx,
);
// Make sure we're synchronously storing the pending invite
assert!(store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
@ -103,12 +111,12 @@ async fn test_core_channels(
&[
(
client_a.user_id().unwrap(),
true,
proto::ChannelRole::Admin,
proto::channel_member::Kind::Member,
),
(
client_b.user_id().unwrap(),
false,
proto::ChannelRole::Member,
proto::channel_member::Kind::Invitee,
),
],
@ -183,7 +191,12 @@ async fn test_core_channels(
client_a
.channel_store()
.update(cx_a, |store, cx| {
store.set_member_admin(channel_a_id, client_b.user_id().unwrap(), true, cx)
store.set_member_role(
channel_a_id,
client_b.user_id().unwrap(),
proto::ChannelRole::Admin,
cx,
)
})
.await
.unwrap();
@ -305,12 +318,12 @@ fn assert_participants_eq(participants: &[Arc<User>], expected_partitipants: &[u
#[track_caller]
fn assert_members_eq(
members: &[ChannelMembership],
expected_members: &[(u64, bool, proto::channel_member::Kind)],
expected_members: &[(u64, proto::ChannelRole, proto::channel_member::Kind)],
) {
assert_eq!(
members
.iter()
.map(|member| (member.user.id, member.admin, member.kind))
.map(|member| (member.user.id, member.role, member.kind))
.collect::<Vec<_>>(),
expected_members
);
@ -611,7 +624,12 @@ async fn test_permissions_update_while_invited(
client_a
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.invite_member(rust_id, client_b.user_id().unwrap(), false, cx)
channel_store.invite_member(
rust_id,
client_b.user_id().unwrap(),
proto::ChannelRole::Member,
cx,
)
})
.await
.unwrap();
@ -634,7 +652,12 @@ async fn test_permissions_update_while_invited(
client_a
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.set_member_admin(rust_id, client_b.user_id().unwrap(), true, cx)
channel_store.set_member_role(
rust_id,
client_b.user_id().unwrap(),
proto::ChannelRole::Admin,
cx,
)
})
.await
.unwrap();
@ -803,7 +826,12 @@ async fn test_lost_channel_creation(
client_a
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.invite_member(channel_id, client_b.user_id().unwrap(), false, cx)
channel_store.invite_member(
channel_id,
client_b.user_id().unwrap(),
proto::ChannelRole::Member,
cx,
)
})
.await
.unwrap();
@ -884,6 +912,119 @@ async fn test_lost_channel_creation(
],
);
}
#[gpui::test]
async fn test_guest_access(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
let channels = server
.make_channel_tree(&[("channel-a", None)], (&client_a, cx_a))
.await;
let channel_a_id = channels[0];
let active_call_b = cx_b.read(ActiveCall::global);
// should not be allowed to join
assert!(active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_a_id, cx))
.await
.is_err());
client_a
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.set_channel_visibility(channel_a_id, proto::ChannelVisibility::Public, cx)
})
.await
.unwrap();
active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_a_id, cx))
.await
.unwrap();
deterministic.run_until_parked();
assert!(client_b
.channel_store()
.update(cx_b, |channel_store, _| channel_store
.channel_for_id(channel_a_id)
.is_some()));
client_a.channel_store().update(cx_a, |channel_store, _| {
let participants = channel_store.channel_participants(channel_a_id);
assert_eq!(participants.len(), 1);
assert_eq!(participants[0].id, client_b.user_id().unwrap());
})
}
#[gpui::test]
async fn test_invite_access(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
let channels = server
.make_channel_tree(
&[("channel-a", None), ("channel-b", Some("channel-a"))],
(&client_a, cx_a),
)
.await;
let channel_a_id = channels[0];
let channel_b_id = channels[0];
let active_call_b = cx_b.read(ActiveCall::global);
// should not be allowed to join
assert!(active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
.await
.is_err());
client_a
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.invite_member(
channel_a_id,
client_b.user_id().unwrap(),
ChannelRole::Member,
cx,
)
})
.await
.unwrap();
active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
.await
.unwrap();
deterministic.run_until_parked();
client_b.channel_store().update(cx_b, |channel_store, _| {
assert!(channel_store.channel_for_id(channel_b_id).is_some());
assert!(channel_store.channel_for_id(channel_a_id).is_some());
});
client_a.channel_store().update(cx_a, |channel_store, _| {
let participants = channel_store.channel_participants(channel_b_id);
assert_eq!(participants.len(), 1);
assert_eq!(participants[0].id, client_b.user_id().unwrap());
})
}
#[gpui::test]
async fn test_channel_moving(

View file

@ -2,7 +2,7 @@ use crate::tests::TestServer;
use gpui::{executor::Deterministic, TestAppContext};
use notifications::NotificationEvent;
use parking_lot::Mutex;
use rpc::Notification;
use rpc::{proto, Notification};
use std::sync::Arc;
#[gpui::test]
@ -120,7 +120,7 @@ async fn test_notifications(
client_a
.channel_store()
.update(cx_a, |store, cx| {
store.invite_member(channel_id, client_b.id(), false, cx)
store.invite_member(channel_id, client_b.id(), proto::ChannelRole::Member, cx)
})
.await
.unwrap();

View file

@ -1,3 +1,5 @@
use crate::db::ChannelRole;
use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
use anyhow::Result;
use async_trait::async_trait;
@ -50,7 +52,7 @@ impl RandomizedTest for RandomChannelBufferTest {
.await
.unwrap();
for user in &users[1..] {
db.invite_channel_member(id, user.user_id, users[0].user_id, false)
db.invite_channel_member(id, user.user_id, users[0].user_id, ChannelRole::Member)
.await
.unwrap();
db.respond_to_channel_invite(id, user.user_id, true)

View file

@ -19,7 +19,7 @@ use node_runtime::FakeNodeRuntime;
use notifications::NotificationStore;
use parking_lot::Mutex;
use project::{Project, WorktreeId};
use rpc::RECEIVE_TIMEOUT;
use rpc::{proto::ChannelRole, RECEIVE_TIMEOUT};
use settings::SettingsStore;
use std::{
cell::{Ref, RefCell, RefMut},
@ -330,7 +330,7 @@ impl TestServer {
channel_store.invite_member(
channel_id,
member_client.user_id().unwrap(),
false,
ChannelRole::Member,
cx,
)
})
@ -623,7 +623,12 @@ impl TestClient {
cx_self
.read(ChannelStore::global)
.update(cx_self, |channel_store, cx| {
channel_store.invite_member(channel, other_client.user_id().unwrap(), true, cx)
channel_store.invite_member(
channel,
other_client.user_id().unwrap(),
ChannelRole::Admin,
cx,
)
})
.await
.unwrap();