Push role refactoring through RPC/client

Conrad Irwin created

Change summary

.cargo/config.toml                                 |  2 
crates/channel/src/channel_store.rs                | 24 +++---
crates/channel/src/channel_store_tests.rs          |  4 
crates/collab/src/db/ids.rs                        | 28 +++++++
crates/collab/src/db/queries/channels.rs           | 18 +++-
crates/collab/src/db/tests/channel_tests.rs        | 10 +-
crates/collab/src/rpc.rs                           | 46 ++++++------
crates/collab/src/tests/channel_tests.rs           | 43 +++++++++--
crates/collab/src/tests/test_server.rs             | 11 ++
crates/collab_ui/src/collab_panel/channel_modal.rs | 57 ++++++++++-----
crates/rpc/proto/zed.proto                         | 20 +++-
crates/rpc/src/proto.rs                            |  4 
12 files changed, 178 insertions(+), 89 deletions(-)

Detailed changes

.cargo/config.toml 🔗

@@ -3,4 +3,4 @@ xtask = "run --package xtask --"
 
 [build]
 # v0 mangling scheme provides more detailed backtraces around closures
-rustflags = ["-C", "symbol-mangling-version=v0"]
+rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"]

crates/channel/src/channel_store.rs 🔗

@@ -9,7 +9,7 @@ use db::RELEASE_CHANNEL;
 use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
 use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
 use rpc::{
-    proto::{self, ChannelEdge, ChannelPermission},
+    proto::{self, ChannelEdge, ChannelPermission, ChannelRole},
     TypedEnvelope,
 };
 use serde_derive::{Deserialize, Serialize};
@@ -79,7 +79,7 @@ pub struct ChannelPath(Arc<[ChannelId]>);
 pub struct ChannelMembership {
     pub user: Arc<User>,
     pub kind: proto::channel_member::Kind,
-    pub admin: bool,
+    pub role: proto::ChannelRole,
 }
 
 pub enum ChannelEvent {
@@ -436,7 +436,7 @@ impl ChannelStore {
                         insert_edge: parent_edge,
                         channel_permissions: vec![ChannelPermission {
                             channel_id,
-                            is_admin: true,
+                            role: ChannelRole::Admin.into(),
                         }],
                         ..Default::default()
                     },
@@ -512,7 +512,7 @@ impl ChannelStore {
         &mut self,
         channel_id: ChannelId,
         user_id: UserId,
-        admin: bool,
+        role: proto::ChannelRole,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if !self.outgoing_invites.insert((channel_id, user_id)) {
@@ -526,7 +526,7 @@ impl ChannelStore {
                 .request(proto::InviteChannelMember {
                     channel_id,
                     user_id,
-                    admin,
+                    role: role.into(),
                 })
                 .await;
 
@@ -570,11 +570,11 @@ impl ChannelStore {
         })
     }
 
-    pub fn set_member_admin(
+    pub fn set_member_role(
         &mut self,
         channel_id: ChannelId,
         user_id: UserId,
-        admin: bool,
+        role: proto::ChannelRole,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if !self.outgoing_invites.insert((channel_id, user_id)) {
@@ -585,10 +585,10 @@ impl ChannelStore {
         let client = self.client.clone();
         cx.spawn(|this, mut cx| async move {
             let result = client
-                .request(proto::SetChannelMemberAdmin {
+                .request(proto::SetChannelMemberRole {
                     channel_id,
                     user_id,
-                    admin,
+                    role: role.into(),
                 })
                 .await;
 
@@ -676,8 +676,8 @@ impl ChannelStore {
                 .filter_map(|(user, member)| {
                     Some(ChannelMembership {
                         user,
-                        admin: member.admin,
-                        kind: proto::channel_member::Kind::from_i32(member.kind)?,
+                        role: member.role(),
+                        kind: member.kind(),
                     })
                 })
                 .collect())
@@ -935,7 +935,7 @@ impl ChannelStore {
         }
 
         for permission in payload.channel_permissions {
-            if permission.is_admin {
+            if permission.role() == proto::ChannelRole::Admin {
                 self.channels_with_admin_privileges
                     .insert(permission.channel_id);
             } else {

crates/channel/src/channel_store_tests.rs 🔗

@@ -26,7 +26,7 @@ fn test_update_channels(cx: &mut AppContext) {
             ],
             channel_permissions: vec![proto::ChannelPermission {
                 channel_id: 1,
-                is_admin: true,
+                role: proto::ChannelRole::Admin.into(),
             }],
             ..Default::default()
         },
@@ -114,7 +114,7 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
             ],
             channel_permissions: vec![proto::ChannelPermission {
                 channel_id: 0,
-                is_admin: true,
+                role: proto::ChannelRole::Admin.into(),
             }],
             ..Default::default()
         },

crates/collab/src/db/ids.rs 🔗

@@ -1,4 +1,5 @@
 use crate::Result;
+use rpc::proto;
 use sea_orm::{entity::prelude::*, DbErr};
 use serde::{Deserialize, Serialize};
 
@@ -91,3 +92,30 @@ pub enum ChannelRole {
     #[sea_orm(string_value = "guest")]
     Guest,
 }
+
+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,
+        }
+    }
+}
+
+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,
+        }
+    }
+}
+
+impl Into<i32> for ChannelRole {
+    fn into(self) -> i32 {
+        let proto: proto::ChannelRole = self.into();
+        proto.into()
+    }
+}

crates/collab/src/db/queries/channels.rs 🔗

@@ -564,13 +564,18 @@ impl Database {
                     (false, true) => proto::channel_member::Kind::AncestorMember,
                     (false, false) => continue,
                 };
+                let channel_role = channel_role.unwrap_or(if is_admin {
+                    ChannelRole::Admin
+                } else {
+                    ChannelRole::Member
+                });
                 let user_id = user_id.to_proto();
                 let kind = kind.into();
                 if let Some(last_row) = rows.last_mut() {
                     if last_row.user_id == user_id {
                         if is_direct_member {
                             last_row.kind = kind;
-                            last_row.admin = channel_role == Some(ChannelRole::Admin) || is_admin;
+                            last_row.role = channel_role.into()
                         }
                         continue;
                     }
@@ -578,7 +583,7 @@ impl Database {
                 rows.push(proto::ChannelMember {
                     user_id,
                     kind,
-                    admin: channel_role == Some(ChannelRole::Admin) || is_admin,
+                    role: channel_role.into(),
                 });
             }
 
@@ -851,10 +856,11 @@ impl Database {
         &self,
         user: UserId,
         channel: ChannelId,
-        to: ChannelId,
+        new_parent: ChannelId,
         tx: &DatabaseTransaction,
     ) -> Result<ChannelGraph> {
-        self.check_user_is_channel_admin(to, user, &*tx).await?;
+        self.check_user_is_channel_admin(new_parent, user, &*tx)
+            .await?;
 
         let paths = channel_path::Entity::find()
             .filter(channel_path::Column::IdPath.like(&format!("%/{}/%", channel)))
@@ -872,7 +878,7 @@ impl Database {
         }
 
         let paths_to_new_parent = channel_path::Entity::find()
-            .filter(channel_path::Column::ChannelId.eq(to))
+            .filter(channel_path::Column::ChannelId.eq(new_parent))
             .all(tx)
             .await?;
 
@@ -906,7 +912,7 @@ impl Database {
         if let Some(channel) = channel_descendants.get_mut(&channel) {
             // Remove the other parents
             channel.clear();
-            channel.insert(to);
+            channel.insert(new_parent);
         }
 
         let channels = self

crates/collab/src/db/tests/channel_tests.rs 🔗

@@ -328,17 +328,17 @@ 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::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(),
             },
         ]
     );
@@ -362,12 +362,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(),
             },
         ]
     );

crates/collab/src/rpc.rs 🔗

@@ -3,8 +3,8 @@ mod connection_pool;
 use crate::{
     auth,
     db::{
-        self, BufferId, ChannelId, ChannelRole, ChannelsForUser, Database, MessageId, ProjectId,
-        RoomId, ServerId, User, UserId,
+        self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId,
+        ServerId, User, UserId,
     },
     executor::Executor,
     AppState, Result,
@@ -254,7 +254,7 @@ 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(rename_channel)
             .add_request_handler(join_channel_buffer)
             .add_request_handler(leave_channel_buffer)
@@ -2282,13 +2282,13 @@ 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);
-    let role = if request.admin {
-        ChannelRole::Admin
-    } else {
-        ChannelRole::Member
-    };
-    db.invite_channel_member(channel_id, invitee_id, session.user_id, role)
-        .await?;
+    db.invite_channel_member(
+        channel_id,
+        invitee_id,
+        session.user_id,
+        request.role().into(),
+    )
+    .await?;
 
     let (channel, _) = db
         .get_channel(channel_id, session.user_id)
@@ -2339,21 +2339,21 @@ async fn remove_channel_member(
     Ok(())
 }
 
-async fn set_channel_member_admin(
-    request: proto::SetChannelMemberAdmin,
-    response: Response<proto::SetChannelMemberAdmin>,
+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);
-    let role = if request.admin {
-        ChannelRole::Admin
-    } else {
-        ChannelRole::Member
-    };
-    db.set_channel_member_role(channel_id, session.user_id, member_id, role)
-        .await?;
+    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)
@@ -2364,7 +2364,7 @@ async fn set_channel_member_admin(
     if has_accepted {
         update.channel_permissions.push(proto::ChannelPermission {
             channel_id: channel.id.to_proto(),
-            is_admin: request.admin,
+            role: request.role,
         });
     }
 
@@ -2603,7 +2603,7 @@ async fn respond_to_channel_invite(
                     .into_iter()
                     .map(|channel_id| proto::ChannelPermission {
                         channel_id: channel_id.to_proto(),
-                        is_admin: true,
+                        role: proto::ChannelRole::Admin.into(),
                     }),
             );
     }
@@ -3106,7 +3106,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(),
                 }),
         );
 

crates/collab/src/tests/channel_tests.rs 🔗

@@ -68,7 +68,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 +108,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 +188,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 +315,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 +621,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 +649,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 +823,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();

crates/collab/src/tests/test_server.rs 🔗

@@ -17,7 +17,7 @@ use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext, WindowHan
 use language::LanguageRegistry;
 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},
@@ -325,7 +325,7 @@ impl TestServer {
                     channel_store.invite_member(
                         channel_id,
                         member_client.user_id().unwrap(),
-                        false,
+                        ChannelRole::Member,
                         cx,
                     )
                 })
@@ -613,7 +613,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();

crates/collab_ui/src/collab_panel/channel_modal.rs 🔗

@@ -1,5 +1,8 @@
 use channel::{ChannelId, ChannelMembership, ChannelStore};
-use client::{proto, User, UserId, UserStore};
+use client::{
+    proto::{self, ChannelRole},
+    User, UserId, UserStore,
+};
 use context_menu::{ContextMenu, ContextMenuItem};
 use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
@@ -343,9 +346,11 @@ impl PickerDelegate for ChannelModalDelegate {
     }
 
     fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if let Some((selected_user, admin)) = self.user_at_index(self.selected_index) {
+        if let Some((selected_user, role)) = self.user_at_index(self.selected_index) {
             match self.mode {
-                Mode::ManageMembers => self.show_context_menu(admin.unwrap_or(false), cx),
+                Mode::ManageMembers => {
+                    self.show_context_menu(role.unwrap_or(ChannelRole::Member), cx)
+                }
                 Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
                     Some(proto::channel_member::Kind::Invitee) => {
                         self.remove_selected_member(cx);
@@ -373,7 +378,7 @@ impl PickerDelegate for ChannelModalDelegate {
         let full_theme = &theme::current(cx);
         let theme = &full_theme.collab_panel.channel_modal;
         let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
-        let (user, admin) = self.user_at_index(ix).unwrap();
+        let (user, role) = self.user_at_index(ix).unwrap();
         let request_status = self.member_status(user.id, cx);
 
         let style = tabbed_modal
@@ -409,15 +414,25 @@ impl PickerDelegate for ChannelModalDelegate {
                     },
                 )
             })
-            .with_children(admin.and_then(|admin| {
-                (in_manage && admin).then(|| {
+            .with_children(if in_manage && role == Some(ChannelRole::Admin) {
+                Some(
                     Label::new("Admin", theme.member_tag.text.clone())
                         .contained()
                         .with_style(theme.member_tag.container)
                         .aligned()
-                        .left()
-                })
-            }))
+                        .left(),
+                )
+            } else if in_manage && role == Some(ChannelRole::Guest) {
+                Some(
+                    Label::new("Guest", theme.member_tag.text.clone())
+                        .contained()
+                        .with_style(theme.member_tag.container)
+                        .aligned()
+                        .left(),
+                )
+            } else {
+                None
+            })
             .with_children({
                 let svg = match self.mode {
                     Mode::ManageMembers => Some(
@@ -502,13 +517,13 @@ impl ChannelModalDelegate {
             })
     }
 
-    fn user_at_index(&self, ix: usize) -> Option<(Arc<User>, Option<bool>)> {
+    fn user_at_index(&self, ix: usize) -> Option<(Arc<User>, Option<ChannelRole>)> {
         match self.mode {
             Mode::ManageMembers => self.matching_member_indices.get(ix).and_then(|ix| {
                 let channel_membership = self.members.get(*ix)?;
                 Some((
                     channel_membership.user.clone(),
-                    Some(channel_membership.admin),
+                    Some(channel_membership.role),
                 ))
             }),
             Mode::InviteMembers => Some((self.matching_users.get(ix).cloned()?, None)),
@@ -516,17 +531,21 @@ impl ChannelModalDelegate {
     }
 
     fn toggle_selected_member_admin(&mut self, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
-        let (user, admin) = self.user_at_index(self.selected_index)?;
-        let admin = !admin.unwrap_or(false);
+        let (user, role) = self.user_at_index(self.selected_index)?;
+        let new_role = if role == Some(ChannelRole::Admin) {
+            ChannelRole::Member
+        } else {
+            ChannelRole::Admin
+        };
         let update = self.channel_store.update(cx, |store, cx| {
-            store.set_member_admin(self.channel_id, user.id, admin, cx)
+            store.set_member_role(self.channel_id, user.id, new_role, cx)
         });
         cx.spawn(|picker, mut cx| async move {
             update.await?;
             picker.update(&mut cx, |picker, cx| {
                 let this = picker.delegate_mut();
                 if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) {
-                    member.admin = admin;
+                    member.role = new_role;
                 }
                 cx.focus_self();
                 cx.notify();
@@ -572,7 +591,7 @@ impl ChannelModalDelegate {
 
     fn invite_member(&mut self, user: Arc<User>, cx: &mut ViewContext<Picker<Self>>) {
         let invite_member = self.channel_store.update(cx, |store, cx| {
-            store.invite_member(self.channel_id, user.id, false, cx)
+            store.invite_member(self.channel_id, user.id, ChannelRole::Member, cx)
         });
 
         cx.spawn(|this, mut cx| async move {
@@ -582,7 +601,7 @@ impl ChannelModalDelegate {
                 this.delegate_mut().members.push(ChannelMembership {
                     user,
                     kind: proto::channel_member::Kind::Invitee,
-                    admin: false,
+                    role: ChannelRole::Member,
                 });
                 cx.notify();
             })
@@ -590,7 +609,7 @@ impl ChannelModalDelegate {
         .detach_and_log_err(cx);
     }
 
-    fn show_context_menu(&mut self, user_is_admin: bool, cx: &mut ViewContext<Picker<Self>>) {
+    fn show_context_menu(&mut self, role: ChannelRole, cx: &mut ViewContext<Picker<Self>>) {
         self.context_menu.update(cx, |context_menu, cx| {
             context_menu.show(
                 Default::default(),
@@ -598,7 +617,7 @@ impl ChannelModalDelegate {
                 vec![
                     ContextMenuItem::action("Remove", RemoveMember),
                     ContextMenuItem::action(
-                        if user_is_admin {
+                        if role == ChannelRole::Admin {
                             "Make non-admin"
                         } else {
                             "Make admin"

crates/rpc/proto/zed.proto 🔗

@@ -144,7 +144,7 @@ message Envelope {
         DeleteChannel delete_channel = 118;
         GetChannelMembers get_channel_members = 119;
         GetChannelMembersResponse get_channel_members_response = 120;
-        SetChannelMemberAdmin set_channel_member_admin = 121;
+        SetChannelMemberRole set_channel_member_role = 145;
         RenameChannel rename_channel = 122;
         RenameChannelResponse rename_channel_response = 123;
 
@@ -170,7 +170,7 @@ message Envelope {
 
         LinkChannel link_channel = 140;
         UnlinkChannel unlink_channel = 141;
-        MoveChannel move_channel = 142; // current max: 144
+        MoveChannel move_channel = 142; // current max: 145
     }
 }
 
@@ -979,7 +979,7 @@ message ChannelEdge {
 
 message ChannelPermission {
     uint64 channel_id = 1;
-    bool is_admin = 2;
+    ChannelRole role = 3;
 }
 
 message ChannelParticipants {
@@ -1005,8 +1005,8 @@ message GetChannelMembersResponse {
 
 message ChannelMember {
     uint64 user_id = 1;
-    bool admin = 2;
     Kind kind = 3;
+    ChannelRole role = 4;
 
     enum Kind {
         Member = 0;
@@ -1028,7 +1028,7 @@ message CreateChannelResponse {
 message InviteChannelMember {
     uint64 channel_id = 1;
     uint64 user_id = 2;
-    bool admin = 3;
+    ChannelRole role = 4;
 }
 
 message RemoveChannelMember {
@@ -1036,10 +1036,16 @@ message RemoveChannelMember {
     uint64 user_id = 2;
 }
 
-message SetChannelMemberAdmin {
+enum ChannelRole {
+    Admin = 0;
+    Member = 1;
+    Guest = 2;
+}
+
+message SetChannelMemberRole {
     uint64 channel_id = 1;
     uint64 user_id = 2;
-    bool admin = 3;
+    ChannelRole role = 3;
 }
 
 message RenameChannel {

crates/rpc/src/proto.rs 🔗

@@ -230,7 +230,7 @@ messages!(
     (SaveBuffer, Foreground),
     (RenameChannel, Foreground),
     (RenameChannelResponse, Foreground),
-    (SetChannelMemberAdmin, Foreground),
+    (SetChannelMemberRole, Foreground),
     (SearchProject, Background),
     (SearchProjectResponse, Background),
     (ShareProject, Foreground),
@@ -326,7 +326,7 @@ request_messages!(
     (RemoveContact, Ack),
     (RespondToContactRequest, Ack),
     (RespondToChannelInvite, Ack),
-    (SetChannelMemberAdmin, Ack),
+    (SetChannelMemberRole, Ack),
     (SendChannelMessage, SendChannelMessageResponse),
     (GetChannelMessages, GetChannelMessagesResponse),
     (GetChannelMembers, GetChannelMembersResponse),