diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 3a9d04812f0dad96bef6d33f049aa075136b7103..b13e682d91bc8b2ce2860ad3d9c1b6d60e620cb2 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -3,9 +3,7 @@ mod channel_index; use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage}; use anyhow::{anyhow, Result}; use channel_index::ChannelIndex; -use client::{ - ChannelId, Client, ClientSettings, HostedProjectId, Subscription, User, UserId, UserStore, -}; +use client::{ChannelId, Client, ClientSettings, ProjectId, Subscription, User, UserId, UserStore}; use collections::{hash_map, HashMap, HashSet}; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use gpui::{ @@ -37,7 +35,7 @@ struct NotesVersion { #[derive(Debug, Clone)] pub struct HostedProject { - id: HostedProjectId, + project_id: ProjectId, channel_id: ChannelId, name: SharedString, _visibility: proto::ChannelVisibility, @@ -46,7 +44,7 @@ pub struct HostedProject { impl From for HostedProject { fn from(project: proto::HostedProject) -> Self { Self { - id: HostedProjectId(project.id), + project_id: ProjectId(project.project_id), channel_id: ChannelId(project.channel_id), _visibility: project.visibility(), name: project.name.into(), @@ -59,7 +57,7 @@ pub struct ChannelStore { channel_invitations: Vec>, channel_participants: HashMap>>, channel_states: HashMap, - hosted_projects: HashMap, + hosted_projects: HashMap, outgoing_invites: HashSet<(ChannelId, UserId)>, update_channels_tx: mpsc::UnboundedSender, @@ -88,7 +86,7 @@ pub struct ChannelState { observed_notes_version: NotesVersion, observed_chat_message: Option, role: Option, - projects: HashSet, + projects: HashSet, } impl Channel { @@ -305,8 +303,8 @@ impl ChannelStore { self.channel_index.by_id().get(&channel_id) } - pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, HostedProjectId)> { - let mut projects: Vec<(SharedString, HostedProjectId)> = self + pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, ProjectId)> { + let mut projects: Vec<(SharedString, ProjectId)> = self .channel_states .get(&channel_id) .map(|state| state.projects.clone()) @@ -1159,27 +1157,27 @@ impl ChannelStore { let hosted_project: HostedProject = hosted_project.into(); if let Some(old_project) = self .hosted_projects - .insert(hosted_project.id, hosted_project.clone()) + .insert(hosted_project.project_id, hosted_project.clone()) { self.channel_states .entry(old_project.channel_id) .or_default() - .remove_hosted_project(old_project.id); + .remove_hosted_project(old_project.project_id); } self.channel_states .entry(hosted_project.channel_id) .or_default() - .add_hosted_project(hosted_project.id); + .add_hosted_project(hosted_project.project_id); } for hosted_project_id in payload.deleted_hosted_projects { - let hosted_project_id = HostedProjectId(hosted_project_id); + let hosted_project_id = ProjectId(hosted_project_id); if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) { self.channel_states .entry(old_project.channel_id) .or_default() - .remove_hosted_project(old_project.id); + .remove_hosted_project(old_project.project_id); } } } @@ -1289,11 +1287,11 @@ impl ChannelState { } } - fn add_hosted_project(&mut self, project_id: HostedProjectId) { + fn add_hosted_project(&mut self, project_id: ProjectId) { self.projects.insert(project_id); } - fn remove_hosted_project(&mut self, project_id: HostedProjectId) { + fn remove_hosted_project(&mut self, project_id: ProjectId) { self.projects.remove(&project_id); } } diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 0c10e50ac5c89b0162b4b3c5acb3cb37c4df3b69..e8be09dd642e52afcd97a1083c95a66658a63ca7 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -25,7 +25,7 @@ impl std::fmt::Display for ChannelId { } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct HostedProjectId(pub u64); +pub struct ProjectId(pub u64); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ParticipantIndex(pub u32); diff --git a/crates/collab/src/db/queries/hosted_projects.rs b/crates/collab/src/db/queries/hosted_projects.rs index ca91e2822ea6b772d58e95d2f8203d123ae65505..9ddafe3be1b771087607a01eeae823597f00be6e 100644 --- a/crates/collab/src/db/queries/hosted_projects.rs +++ b/crates/collab/src/db/queries/hosted_projects.rs @@ -9,20 +9,21 @@ impl Database { roles: &HashMap, tx: &DatabaseTransaction, ) -> Result> { - Ok(hosted_project::Entity::find() + let projects = hosted_project::Entity::find() + .find_also_related(project::Entity) .filter(hosted_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0))) .all(tx) .await? .into_iter() - .flat_map(|project| { - if project.deleted_at.is_some() { + .flat_map(|(hosted_project, project)| { + if hosted_project.deleted_at.is_some() { return None; } - match project.visibility { + match hosted_project.visibility { ChannelVisibility::Public => {} ChannelVisibility::Members => { let is_visible = roles - .get(&project.channel_id) + .get(&hosted_project.channel_id) .map(|role| role.can_see_all_descendants()) .unwrap_or(false); if !is_visible { @@ -31,13 +32,15 @@ impl Database { } }; Some(proto::HostedProject { - id: project.id.to_proto(), - channel_id: project.channel_id.to_proto(), - name: project.name.clone(), - visibility: project.visibility.into(), + project_id: project?.id.to_proto(), + channel_id: hosted_project.channel_id.to_proto(), + name: hosted_project.name.clone(), + visibility: hosted_project.visibility.into(), }) }) - .collect()) + .collect(); + + Ok(projects) } pub async fn get_hosted_project( diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index c051882d95eab4a3ba299e1936bdf209dccdcda4..6bd7022a799c6f9866d43c38c86559ff565c5676 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -512,18 +512,30 @@ impl Database { /// Adds the given connection to the specified hosted project pub async fn join_hosted_project( &self, - id: HostedProjectId, + id: ProjectId, user_id: UserId, connection: ConnectionId, ) -> Result<(Project, ReplicaId)> { self.transaction(|tx| async move { - let (hosted_project, role) = self.get_hosted_project(id, user_id, &tx).await?; - let project = project::Entity::find() - .filter(project::Column::HostedProjectId.eq(hosted_project.id)) + let (project, hosted_project) = project::Entity::find_by_id(id) + .find_also_related(hosted_project::Entity) .one(&*tx) .await? .ok_or_else(|| anyhow!("hosted project is no longer shared"))?; + let Some(hosted_project) = hosted_project else { + return Err(anyhow!("project is not hosted"))?; + }; + + let channel = channel::Entity::find_by_id(hosted_project.channel_id) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such channel"))?; + + let role = self + .check_user_is_channel_participant(&channel, user_id, &tx) + .await?; + self.join_project_internal(project, user_id, connection, role, &tx) .await }) diff --git a/crates/collab/src/db/tables/hosted_project.rs b/crates/collab/src/db/tables/hosted_project.rs index 265acd80fffca4117039f703980874537dde1d92..dd7cb1b5b107f90d9158afc47051da88aab033e8 100644 --- a/crates/collab/src/db/tables/hosted_project.rs +++ b/crates/collab/src/db/tables/hosted_project.rs @@ -15,4 +15,13 @@ pub struct Model { impl ActiveModelBehavior for ActiveModel {} #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +pub enum Relation { + #[sea_orm(has_one = "super::project::Entity")] + Project, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Project.def() + } +} diff --git a/crates/collab/src/db/tables/project.rs b/crates/collab/src/db/tables/project.rs index 550f8415d75950ff64f18e5692247d48a6eb50b2..a357634aff614ccb6d4377c6ff1a42522e862a5d 100644 --- a/crates/collab/src/db/tables/project.rs +++ b/crates/collab/src/db/tables/project.rs @@ -50,6 +50,12 @@ pub enum Relation { Collaborators, #[sea_orm(has_many = "super::language_server::Entity")] LanguageServers, + #[sea_orm( + belongs_to = "super::hosted_project::Entity", + from = "Column::HostedProjectId", + to = "super::hosted_project::Column::Id" + )] + HostedProject, } impl Related for Entity { @@ -82,4 +88,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::HostedProject.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index c3df8d12fd4c0896cf337c5af36492f677ce358b..63d8bc45ebaf0506790bfd2e38522a39f9ecd80d 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -4,9 +4,9 @@ use crate::{ auth::{self, Impersonator}, db::{ self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database, - HostedProjectId, InviteMemberResult, MembershipUpdated, MessageId, NotificationId, Project, - ProjectId, RemoveChannelMemberResult, ReplicaId, RespondToChannelInvite, RoomId, ServerId, - User, UserId, + InviteMemberResult, MembershipUpdated, MessageId, NotificationId, Project, ProjectId, + RemoveChannelMemberResult, ReplicaId, RespondToChannelInvite, RoomId, ServerId, User, + UserId, }, executor::Executor, AppState, Error, Result, @@ -1770,7 +1770,7 @@ async fn join_hosted_project( .db() .await .join_hosted_project( - HostedProjectId(request.id as i32), + ProjectId(request.project_id as i32), session.user_id, session.connection_id, ) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 4f242d68bb7d157c1c844d4921984376f215588b..f008e858df97a26335f43347d47bf034ecccc2c4 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -8,7 +8,7 @@ use crate::{ }; use call::ActiveCall; use channel::{Channel, ChannelEvent, ChannelStore}; -use client::{ChannelId, Client, Contact, HostedProjectId, User, UserStore}; +use client::{ChannelId, Client, Contact, ProjectId, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; @@ -185,7 +185,7 @@ enum ListEntry { depth: usize, }, HostedProject { - id: HostedProjectId, + id: ProjectId, name: SharedString, }, Contact { @@ -1035,7 +1035,7 @@ impl CollabPanel { fn render_channel_project( &self, - id: HostedProjectId, + id: ProjectId, name: &SharedString, is_selected: bool, cx: &mut ViewContext, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bdca996d945603c8b261a8797893761e6483c507..2f787920b49f46faff6edb52dee600e1d31566d0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -12,8 +12,7 @@ mod project_tests; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use client::{ - proto, Client, Collaborator, HostedProjectId, PendingEntitySubscription, TypedEnvelope, - UserStore, + proto, Client, Collaborator, PendingEntitySubscription, ProjectId, TypedEnvelope, UserStore, }; use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque}; @@ -175,7 +174,7 @@ pub struct Project { prettiers_per_worktree: HashMap>>, prettier_instances: HashMap, tasks: Model, - hosted_project_id: Option, + hosted_project_id: Option, } pub enum LanguageServerToQuery { @@ -780,29 +779,31 @@ impl Project { } pub async fn hosted( - _hosted_project_id: HostedProjectId, - _user_store: Model, - _client: Arc, - _languages: Arc, - _fs: Arc, - _cx: AsyncAppContext, + remote_id: ProjectId, + user_store: Model, + client: Arc, + languages: Arc, + fs: Arc, + cx: AsyncAppContext, ) -> Result> { - // let response = client - // .request_envelope(proto::JoinHostedProject { - // id: hosted_project_id.0, - // }) - // .await?; - // Self::from_join_project_response( - // response, - // Some(hosted_project_id), - // client, - // user_store, - // languages, - // fs, - // cx, - // ) - // .await - Err(anyhow!("disabled")) + client.authenticate_and_connect(true, &cx).await?; + + let subscription = client.subscribe_to_entity(remote_id.0)?; + let response = client + .request_envelope(proto::JoinHostedProject { + project_id: remote_id.0, + }) + .await?; + Self::from_join_project_response( + response, + subscription, + client, + user_store, + languages, + fs, + cx, + ) + .await } fn release(&mut self, cx: &mut AppContext) { @@ -1050,7 +1051,7 @@ impl Project { } } - pub fn hosted_project_id(&self) -> Option { + pub fn hosted_project_id(&self) -> Option { self.hosted_project_id } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index a45e7a5e2117b9b20ba08a032aff4e027b5b9cac..55945f492b976d0196b59351b272cdfa0bac55f0 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -408,7 +408,7 @@ message JoinProject { } message JoinHostedProject { - uint64 id = 1; + uint64 project_id = 1; } message JoinProjectResponse { @@ -1067,7 +1067,7 @@ message ChannelParticipants { } message HostedProject { - uint64 id = 1; + uint64 project_id = 1; uint64 channel_id = 2; string name = 3; ChannelVisibility visibility = 4; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a9e3443c7f1a84101ed1601d0d2e92ce6eac828f..00b167e07bb0c395289d158961bd13a8b8cf6ffc 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -15,7 +15,7 @@ use anyhow::{anyhow, Context as _, Result}; use call::{call_settings::CallSettings, ActiveCall}; use client::{ proto::{self, ErrorCode, PeerId}, - ChannelId, Client, ErrorExt, HostedProjectId, Status, TypedEnvelope, UserStore, + ChannelId, Client, ErrorExt, ProjectId, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use derive_more::{Deref, DerefMut}; @@ -4462,7 +4462,7 @@ pub fn create_and_open_local_file( } pub fn join_hosted_project( - hosted_project_id: HostedProjectId, + hosted_project_id: ProjectId, app_state: Arc, cx: &mut AppContext, ) -> Task> {