Undo making project optional on stored follower states

Max Brunsfeld created

Following works without a project, but following in unshared projects does
not need to be replicated to other participants.

Change summary

crates/call/src/call.rs                                                     |  2 
crates/call/src/room.rs                                                     |  4 
crates/collab/migrations.sqlite/20221109000000_test_schema.sql              |  2 
crates/collab/migrations/20230918142700_allow_following_without_project.sql |  1 
crates/collab/src/db/queries/projects.rs                                    | 91 
crates/collab/src/db/queries/rooms.rs                                       |  2 
crates/collab/src/db/tables/follower.rs                                     |  2 
crates/collab/src/rpc.rs                                                    | 32 
crates/rpc/proto/zed.proto                                                  |  2 
9 files changed, 46 insertions(+), 92 deletions(-)

Detailed changes

crates/call/src/call.rs 🔗

@@ -58,7 +58,7 @@ pub struct ActiveCall {
     _subscriptions: Vec<client::Subscription>,
 }
 
-#[derive(PartialEq, Eq, PartialOrd, Ord)]
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 struct Follower {
     project_id: Option<u64>,
     peer_id: PeerId,

crates/call/src/room.rs 🔗

@@ -62,7 +62,7 @@ pub struct Room {
     leave_when_empty: bool,
     client: Arc<Client>,
     user_store: ModelHandle<UserStore>,
-    follows_by_leader_id_project_id: HashMap<(PeerId, Option<u64>), Vec<PeerId>>,
+    follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
     subscriptions: Vec<client::Subscription>,
     pending_room_update: Option<Task<()>>,
     maintain_connection: Option<Task<Option<()>>>,
@@ -584,7 +584,7 @@ impl Room {
 
     pub fn followers_for(&self, leader_id: PeerId, project_id: u64) -> &[PeerId] {
         self.follows_by_leader_id_project_id
-            .get(&(leader_id, Some(project_id)))
+            .get(&(leader_id, project_id))
             .map_or(&[], |v| v.as_slice())
     }
 

crates/collab/migrations.sqlite/20221109000000_test_schema.sql 🔗

@@ -176,7 +176,7 @@ CREATE TABLE "servers" (
 CREATE TABLE "followers" (
     "id" INTEGER PRIMARY KEY AUTOINCREMENT,
     "room_id" INTEGER NOT NULL REFERENCES rooms (id) ON DELETE CASCADE,
-    "project_id" INTEGER REFERENCES projects (id) ON DELETE CASCADE,
+    "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
     "leader_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
     "leader_connection_id" INTEGER NOT NULL,
     "follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,

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

@@ -862,83 +862,34 @@ impl Database {
         .await
     }
 
-    pub async fn check_can_follow(
+    pub async fn check_room_participants(
         &self,
         room_id: RoomId,
-        project_id: Option<ProjectId>,
         leader_id: ConnectionId,
         follower_id: ConnectionId,
     ) -> Result<()> {
-        let mut found_leader = false;
-        let mut found_follower = false;
         self.transaction(|tx| async move {
-            if let Some(project_id) = project_id {
-                let mut rows = project_collaborator::Entity::find()
-                    .filter(project_collaborator::Column::ProjectId.eq(project_id))
-                    .stream(&*tx)
-                    .await?;
-                while let Some(row) = rows.next().await {
-                    let row = row?;
-                    let connection = row.connection();
-                    if connection == leader_id {
-                        found_leader = true;
-                    } else if connection == follower_id {
-                        found_follower = true;
-                    }
-                }
-            } else {
-                let mut rows = room_participant::Entity::find()
-                    .filter(room_participant::Column::RoomId.eq(room_id))
-                    .stream(&*tx)
-                    .await?;
-                while let Some(row) = rows.next().await {
-                    let row = row?;
-                    if let Some(connection) = row.answering_connection() {
-                        if connection == leader_id {
-                            found_leader = true;
-                        } else if connection == follower_id {
-                            found_follower = true;
-                        }
-                    }
-                }
-            }
+            use room_participant::Column;
 
-            if !found_leader || !found_follower {
-                Err(anyhow!("not a room participant"))?;
-            }
-
-            Ok(())
-        })
-        .await
-    }
-
-    pub async fn check_can_unfollow(
-        &self,
-        room_id: RoomId,
-        project_id: Option<ProjectId>,
-        leader_id: ConnectionId,
-        follower_id: ConnectionId,
-    ) -> Result<()> {
-        self.transaction(|tx| async move {
-            follower::Entity::find()
+            let count = room_participant::Entity::find()
                 .filter(
-                    Condition::all()
-                        .add(follower::Column::RoomId.eq(room_id))
-                        .add(follower::Column::ProjectId.eq(project_id))
-                        .add(follower::Column::LeaderConnectionId.eq(leader_id.id as i32))
-                        .add(follower::Column::FollowerConnectionId.eq(follower_id.id as i32))
-                        .add(
-                            follower::Column::LeaderConnectionServerId
-                                .eq(leader_id.owner_id as i32),
-                        )
-                        .add(
-                            follower::Column::FollowerConnectionServerId
-                                .eq(follower_id.owner_id as i32),
-                        ),
+                    Condition::all().add(Column::RoomId.eq(room_id)).add(
+                        Condition::any()
+                            .add(Column::AnsweringConnectionId.eq(leader_id.id as i32).and(
+                                Column::AnsweringConnectionServerId.eq(leader_id.owner_id as i32),
+                            ))
+                            .add(Column::AnsweringConnectionId.eq(follower_id.id as i32).and(
+                                Column::AnsweringConnectionServerId.eq(follower_id.owner_id as i32),
+                            )),
+                    ),
                 )
-                .one(&*tx)
-                .await?
-                .ok_or_else(|| anyhow!("not a follower"))?;
+                .count(&*tx)
+                .await?;
+
+            if count < 2 {
+                Err(anyhow!("not room participants"))?;
+            }
+
             Ok(())
         })
         .await
@@ -947,7 +898,7 @@ impl Database {
     pub async fn follow(
         &self,
         room_id: RoomId,
-        project_id: Option<ProjectId>,
+        project_id: ProjectId,
         leader_connection: ConnectionId,
         follower_connection: ConnectionId,
     ) -> Result<RoomGuard<proto::Room>> {
@@ -977,7 +928,7 @@ impl Database {
     pub async fn unfollow(
         &self,
         room_id: RoomId,
-        project_id: Option<ProjectId>,
+        project_id: ProjectId,
         leader_connection: ConnectionId,
         follower_connection: ConnectionId,
     ) -> Result<RoomGuard<proto::Room>> {

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

@@ -1154,7 +1154,7 @@ impl Database {
             followers.push(proto::Follower {
                 leader_id: Some(db_follower.leader_connection().into()),
                 follower_id: Some(db_follower.follower_connection().into()),
-                project_id: db_follower.project_id.map(|id| id.to_proto()),
+                project_id: db_follower.project_id.to_proto(),
             });
         }
 

crates/collab/src/db/tables/follower.rs 🔗

@@ -8,7 +8,7 @@ pub struct Model {
     #[sea_orm(primary_key)]
     pub id: FollowerId,
     pub room_id: RoomId,
-    pub project_id: Option<ProjectId>,
+    pub project_id: ProjectId,
     pub leader_connection_server_id: ServerId,
     pub leader_connection_id: i32,
     pub follower_connection_server_id: ServerId,

crates/collab/src/rpc.rs 🔗

@@ -1901,7 +1901,7 @@ async fn follow(
     session
         .db()
         .await
-        .check_can_follow(room_id, project_id, leader_id, session.connection_id)
+        .check_room_participants(room_id, leader_id, session.connection_id)
         .await?;
 
     let mut response_payload = session
@@ -1913,12 +1913,14 @@ async fn follow(
         .retain(|view| view.leader_id != Some(follower_id.into()));
     response.send(response_payload)?;
 
-    let room = session
-        .db()
-        .await
-        .follow(room_id, project_id, leader_id, follower_id)
-        .await?;
-    room_updated(&room, &session.peer);
+    if let Some(project_id) = project_id {
+        let room = session
+            .db()
+            .await
+            .follow(room_id, project_id, leader_id, follower_id)
+            .await?;
+        room_updated(&room, &session.peer);
+    }
 
     Ok(())
 }
@@ -1935,19 +1937,21 @@ async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> {
     session
         .db()
         .await
-        .check_can_unfollow(room_id, project_id, leader_id, session.connection_id)
+        .check_room_participants(room_id, leader_id, session.connection_id)
         .await?;
 
     session
         .peer
         .forward_send(session.connection_id, leader_id, request)?;
 
-    let room = session
-        .db()
-        .await
-        .unfollow(room_id, project_id, leader_id, follower_id)
-        .await?;
-    room_updated(&room, &session.peer);
+    if let Some(project_id) = project_id {
+        let room = session
+            .db()
+            .await
+            .unfollow(room_id, project_id, leader_id, follower_id)
+            .await?;
+        room_updated(&room, &session.peer);
+    }
 
     Ok(())
 }

crates/rpc/proto/zed.proto 🔗

@@ -273,7 +273,7 @@ message ParticipantProject {
 message Follower {
     PeerId leader_id = 1;
     PeerId follower_id = 2;
-    optional uint64 project_id = 3;
+    uint64 project_id = 3;
 }
 
 message ParticipantLocation {