diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 5a84bfd796a88e09769004cc40d0cc6a06e3118a..dd6e80150be6034c02368372d2ff6f6936a00ef7 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -226,6 +226,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 ); diff --git a/crates/collab/migrations/20231011214412_add_guest_role.sql b/crates/collab/migrations/20231011214412_add_guest_role.sql new file mode 100644 index 0000000000000000000000000000000000000000..378590a0f9702fd63630a32957780096e3d7ba56 --- /dev/null +++ b/crates/collab/migrations/20231011214412_add_guest_role.sql @@ -0,0 +1,4 @@ +-- Add migration script here + +ALTER TABLE channel_members ADD COLUMN role TEXT; +UPDATE channel_members SET role = CASE WHEN admin THEN 'admin' ELSE 'member' END; diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 23bb9e53bf9803ba64693f94605a8e87f904c571..747e3a7d3b17642191a0e1067c11cf1d1c3e205c 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -80,3 +80,14 @@ id_type!(SignupId); id_type!(UserId); id_type!(ChannelBufferCollaboratorId); id_type!(FlagId); + +#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(None)")] +pub enum ChannelRole { + #[sea_orm(string_value = "admin")] + Admin, + #[sea_orm(string_value = "member")] + Member, + #[sea_orm(string_value = "guest")] + Guest, +} diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index c576d2406b81279c38561406c3801c02ddaf4377..0fe78209164be20ba0f2f618a6be7190beed1c26 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -74,11 +74,12 @@ impl Database { } channel_member::ActiveModel { + id: ActiveValue::NotSet, channel_id: ActiveValue::Set(channel.id), user_id: ActiveValue::Set(creator_id), accepted: ActiveValue::Set(true), admin: ActiveValue::Set(true), - ..Default::default() + role: ActiveValue::Set(Some(ChannelRole::Admin)), } .insert(&*tx) .await?; @@ -160,18 +161,19 @@ impl Database { channel_id: ChannelId, invitee_id: UserId, inviter_id: UserId, - is_admin: bool, + role: ChannelRole, ) -> Result<()> { self.transaction(move |tx| async move { self.check_user_is_channel_admin(channel_id, inviter_id, &*tx) .await?; channel_member::ActiveModel { + id: ActiveValue::NotSet, channel_id: ActiveValue::Set(channel_id), user_id: ActiveValue::Set(invitee_id), accepted: ActiveValue::Set(false), - admin: ActiveValue::Set(is_admin), - ..Default::default() + admin: ActiveValue::Set(role == ChannelRole::Admin), + role: ActiveValue::Set(Some(role)), } .insert(&*tx) .await?; @@ -417,7 +419,13 @@ impl Database { let channels_with_admin_privileges = channel_memberships .iter() - .filter_map(|membership| membership.admin.then_some(membership.channel_id)) + .filter_map(|membership| { + if membership.role == Some(ChannelRole::Admin) || membership.admin { + Some(membership.channel_id) + } else { + None + } + }) .collect(); let graph = self @@ -470,12 +478,12 @@ impl Database { .await } - pub async fn set_channel_member_admin( + pub async fn set_channel_member_role( &self, channel_id: ChannelId, from: UserId, for_user: UserId, - admin: bool, + role: ChannelRole, ) -> Result<()> { self.transaction(|tx| async move { self.check_user_is_channel_admin(channel_id, from, &*tx) @@ -488,7 +496,8 @@ impl Database { .and(channel_member::Column::UserId.eq(for_user)), ) .set(channel_member::ActiveModel { - admin: ActiveValue::set(admin), + admin: ActiveValue::set(role == ChannelRole::Admin), + role: ActiveValue::set(Some(role)), ..Default::default() }) .exec(&*tx) @@ -516,6 +525,7 @@ impl Database { enum QueryMemberDetails { UserId, Admin, + Role, IsDirectMember, Accepted, } @@ -528,6 +538,7 @@ impl Database { .select_only() .column(channel_member::Column::UserId) .column(channel_member::Column::Admin) + .column(channel_member::Column::Role) .column_as( channel_member::Column::ChannelId.eq(channel_id), QueryMemberDetails::IsDirectMember, @@ -540,9 +551,10 @@ impl Database { let mut rows = Vec::::new(); while let Some(row) = stream.next().await { - let (user_id, is_admin, is_direct_member, is_invite_accepted): ( + let (user_id, is_admin, channel_role, is_direct_member, is_invite_accepted): ( UserId, bool, + Option, bool, bool, ) = row?; @@ -558,7 +570,7 @@ impl Database { if last_row.user_id == user_id { if is_direct_member { last_row.kind = kind; - last_row.admin = is_admin; + last_row.admin = channel_role == Some(ChannelRole::Admin) || is_admin; } continue; } @@ -566,7 +578,7 @@ impl Database { rows.push(proto::ChannelMember { user_id, kind, - admin: is_admin, + admin: channel_role == Some(ChannelRole::Admin) || is_admin, }); } diff --git a/crates/collab/src/db/tables/channel_member.rs b/crates/collab/src/db/tables/channel_member.rs index ba3db5a15504e60838439cce60e5bef9a02d83bb..e8162bfcbd133fe647dbbfa13cab1136dcbe441e 100644 --- a/crates/collab/src/db/tables/channel_member.rs +++ b/crates/collab/src/db/tables/channel_member.rs @@ -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)] @@ -10,6 +10,8 @@ pub struct Model { pub user_id: UserId, pub accepted: bool, pub admin: bool, + // only optional while migrating + pub role: Option, } impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/db/tests/buffer_tests.rs b/crates/collab/src/db/tests/buffer_tests.rs index 0ac41a8b0b4267fdd50e8e2c8392319169194888..51ba9bf655221a5c611ad3fa023631f46151f144 100644 --- a/crates/collab/src/db/tests/buffer_tests.rs +++ b/crates/collab/src/db/tests/buffer_tests.rs @@ -56,7 +56,7 @@ async fn test_channel_buffers(db: &Arc) { 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(); @@ -211,7 +211,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) diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 7d2bc04a35aac3adb30ead310705d2ee192ed54a..ed4b9e061b0ee7ab1181431b24ffdb1cd88ba5c2 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -8,7 +8,7 @@ use crate::{ db::{ queries::channels::ChannelGraph, tests::{graph, TEST_RELEASE_CHANNEL}, - ChannelId, Database, NewUserParams, + ChannelId, ChannelRole, Database, NewUserParams, }, test_both_dbs, }; @@ -50,7 +50,7 @@ async fn test_channels(db: &Arc) { // 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()); - 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(); @@ -125,9 +125,13 @@ async fn test_channels(db: &Arc) { ); // 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(); @@ -284,13 +288,13 @@ async fn test_channel_invites(db: &Arc) { 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(); diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index e758fcfb5d0104a61ad84dd82ce10fad784fdf5a..272d8e01009ac8758e887d2c0ec4f04570464923 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -1,5 +1,5 @@ use crate::{ - db::{Database, MessageId, NewUserParams}, + db::{ChannelRole, Database, MessageId, NewUserParams}, test_both_dbs, }; use std::sync::Arc; @@ -155,7 +155,7 @@ async fn test_channel_message_new_notification(db: &Arc) { 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(); @@ -163,7 +163,7 @@ async fn test_channel_message_new_notification(db: &Arc) { .await .unwrap(); - db.invite_channel_member(channel_2, observer, user, false) + db.invite_channel_member(channel_2, observer, user, ChannelRole::Member) .await .unwrap(); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index e5c6d94ce03b8b3b1d64ed58be4da53f9dcca112..f13f482c2b3b156401fa20a3b8a8a8a7f51c2f5f 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -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, ChannelRole, ChannelsForUser, Database, MessageId, ProjectId, + RoomId, ServerId, User, UserId, }, executor::Executor, AppState, Result, @@ -2282,7 +2282,12 @@ async fn invite_channel_member( let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); let invitee_id = UserId::from_proto(request.user_id); - db.invite_channel_member(channel_id, invitee_id, session.user_id, request.admin) + let role = if request.admin { + ChannelRole::Admin + } else { + ChannelRole::Member + }; + db.invite_channel_member(channel_id, invitee_id, session.user_id, role) .await?; let (channel, _) = db @@ -2342,7 +2347,12 @@ async fn set_channel_member_admin( 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 role = if request.admin { + ChannelRole::Admin + } else { + ChannelRole::Member + }; + db.set_channel_member_role(channel_id, session.user_id, member_id, role) .await?; let (channel, has_accepted) = db diff --git a/crates/collab/src/tests/random_channel_buffer_tests.rs b/crates/collab/src/tests/random_channel_buffer_tests.rs index 6e0bef225c9fc2d3bc78faa5ddca6e65e28f5495..1b24c7a3d2f45e5e374c5a70cd54a426b4043753 100644 --- a/crates/collab/src/tests/random_channel_buffer_tests.rs +++ b/crates/collab/src/tests/random_channel_buffer_tests.rs @@ -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)