WIP

Max Brunsfeld created

Change summary

crates/collab/migrations.sqlite/20221109000000_test_schema.sql |  26 
crates/collab/migrations/20230727150500_add_channels.sql       |  19 
crates/collab/src/db.rs                                        | 139 +++
crates/collab/src/db/channel.rs                                |  39 +
crates/collab/src/db/channel_member.rs                         |  59 +
crates/collab/src/db/channel_parent.rs                         |  13 
crates/collab/src/db/room.rs                                   |   6 
crates/collab/src/db/user.rs                                   |   8 
crates/collab_ui/src/panel.rs                                  |  10 
9 files changed, 310 insertions(+), 9 deletions(-)

Detailed changes

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

@@ -184,3 +184,29 @@ CREATE UNIQUE INDEX
     "index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id"
 ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id");
 CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");
+
+CREATE TABLE "channels" (
+    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
+    -- "id_path" TEXT NOT NULL,
+    "name" VARCHAR NOT NULL,
+    "room_id" INTEGER REFERENCES rooms (id) ON DELETE SET NULL,
+    "created_at" TIMESTAMP NOT NULL DEFAULT now
+)
+
+CREATE TABLE "channel_parents" (
+    "child_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
+    "parent_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
+    PRIMARY KEY(child_id, parent_id)
+)
+
+-- CREATE UNIQUE INDEX "index_channels_on_id_path" ON "channels" ("id_path");
+
+CREATE TABLE "channel_members" (
+    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
+    "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,
+    "updated_at" TIMESTAMP NOT NULL DEFAULT now
+)
+
+CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");

crates/collab/migrations/20230727150500_add_channels.sql 🔗

@@ -0,0 +1,19 @@
+CREATE TABLE "channels" (
+    "id" SERIAL PRIMARY KEY,
+    "id_path" TEXT NOT NULL,
+    "name" VARCHAR NOT NULL,
+    "room_id" INTEGER REFERENCES rooms (id) ON DELETE SET NULL,
+    "created_at" TIMESTAMP NOT NULL DEFAULT now
+)
+
+CREATE UNIQUE INDEX "index_channels_on_id_path" ON "channels" ("id_path");
+
+CREATE TABLE "channel_members" (
+    "id" SERIAL PRIMARY KEY,
+    "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,
+    "updated_at" TIMESTAMP NOT NULL DEFAULT now
+)
+
+CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");

crates/collab/src/db.rs 🔗

@@ -1,4 +1,7 @@
 mod access_token;
+mod channel;
+mod channel_member;
+mod channel_parent;
 mod contact;
 mod follower;
 mod language_server;
@@ -36,7 +39,7 @@ use sea_orm::{
     DbErr, FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect,
     Statement, TransactionTrait,
 };
-use sea_query::{Alias, Expr, OnConflict, Query};
+use sea_query::{Alias, Expr, OnConflict, Query, SelectStatement};
 use serde::{Deserialize, Serialize};
 pub use signup::{Invite, NewSignup, WaitlistSummary};
 use sqlx::migrate::{Migrate, Migration, MigrationSource};
@@ -3027,6 +3030,138 @@ impl Database {
         .await
     }
 
+    // channels
+
+    pub async fn get_channels(&self, user_id: UserId) -> Result<Vec<ChannelId>> {
+        self.transaction(|tx| async move {
+            let tx = tx;
+
+            let user = user::Model {
+                id: user_id,
+                ..Default::default()
+            };
+            let mut channel_ids = user
+                .find_related(channel_member::Entity)
+                .select_only()
+                .column(channel_member::Column::ChannelId)
+                .all(&*tx)
+                .await;
+
+            let descendants = Alias::new("descendants");
+            let cte_referencing = SelectStatement::new()
+                .column(channel_parent::Column::ChildId)
+                .from(channel::Entity)
+                .and_where(
+                    Expr::col(channel_parent::Column::ParentId)
+                        .in_subquery(SelectStatement::new().from(descendants).take())
+                );
+
+            /*
+            WITH RECURSIVE descendant_ids(id) AS (
+                $1
+                UNION ALL
+                SELECT child_id as id FROM channel_parents WHERE parent_id IN descendants
+            )
+            SELECT * from channels where id in descendant_ids
+            */
+
+
+            // WITH RECURSIVE descendants(id) AS (
+            //    // SQL QUERY FOR SELECTING Initial IDs
+            //   UNION
+            //    SELECT id FROM ancestors WHERE p.parent = id
+            // )
+            // SELECT * FROM descendants;
+
+
+
+            // let descendant_channel_ids =
+
+
+
+            // let query = sea_query::Query::with().recursive(true);
+
+
+            for id_path in id_paths {
+                //
+            }
+
+
+            // zed/public/plugins
+            // zed/public/plugins/js
+            // zed/zed-livekit
+            // livekit/zed-livekit
+            // zed - 101
+            // livekit - 500
+            // zed-livekit - 510
+            // public - 150
+            // plugins - 200
+            // js - 300
+            //
+            // Channel, Parent - edges
+            // 510 - 500
+            // 510 - 101
+            //
+            // Given the channel 'Zed' (101)
+            // Select * from EDGES where parent = 101 => 510
+            //
+
+
+            "SELECT * from channels where id_path like '$1?'"
+
+            // https://www.postgresql.org/docs/current/queries-with.html
+            // https://www.sqlite.org/lang_with.html
+
+            "SELECT channel_id from channel_ancestors where ancestor_id IN $()"
+
+            // | channel_id | ancestor_ids |
+            // 150              150
+            // 150              101
+            // 200              101
+            // 300              101
+            // 200              150
+            // 300              150
+            // 300              200
+            //
+            // // | channel_id | ancestor_ids |
+            // 150              101
+            // 200              101
+            // 300              101
+            // 200              150
+            // 300              [150, 200]
+
+            channel::Entity::find()
+                .filter(channel::Column::IdPath.like(id_paths.unwrap()))
+
+            dbg!(&id_paths.unwrap()[0].id_path);
+
+            // let mut channel_members_by_channel_id = HashMap::new();
+            // for channel_member in channel_members {
+            //     channel_members_by_channel_id
+            //         .entry(channel_member.channel_id)
+            //         .or_insert_with(Vec::new)
+            //         .push(channel_member);
+            // }
+
+            // let mut channel_messages = channel_message::Entity::find()
+            //     .filter(channel_message::Column::ChannelId.in_selection(channel_ids))
+            //     .all(&*tx)
+            //     .await?;
+
+            // let mut channel_messages_by_channel_id = HashMap::new();
+            // for channel_message in channel_messages {
+            //     channel_messages_by_channel_id
+            //         .entry(channel_message.channel_id)
+            //         .or_insert_with(Vec::new)
+            //         .push(channel_message);
+            // }
+
+            todo!();
+            // Ok(channels)
+        })
+        .await
+    }
+
     async fn transaction<F, Fut, T>(&self, f: F) -> Result<T>
     where
         F: Send + Fn(TransactionHandle) -> Fut,
@@ -3400,6 +3535,8 @@ macro_rules! id_type {
 }
 
 id_type!(AccessTokenId);
+id_type!(ChannelId);
+id_type!(ChannelMemberId);
 id_type!(ContactId);
 id_type!(FollowerId);
 id_type!(RoomId);

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

@@ -0,0 +1,39 @@
+use super::{ChannelId, RoomId};
+use sea_orm::entity::prelude::*;
+
+#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "channels")]
+pub struct Model {
+    #[sea_orm(primary_key)]
+    pub id: ChannelId,
+    pub room_id: Option<RoomId>,
+    // pub id_path: String,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(has_one = "super::room::Entity")]
+    Room,
+    #[sea_orm(has_many = "super::channel_member::Entity")]
+    Member,
+}
+
+impl Related<super::channel_member::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Member.def()
+    }
+}
+
+impl Related<super::room::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Room.def()
+    }
+}
+
+// impl Related<super::follower::Entity> for Entity {
+//     fn to() -> RelationDef {
+//         Relation::Follower.def()
+//     }
+// }

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

@@ -0,0 +1,59 @@
+use crate::db::channel_member;
+
+use super::{ChannelId, ChannelMemberId, UserId};
+use sea_orm::entity::prelude::*;
+
+#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "channel_members")]
+pub struct Model {
+    #[sea_orm(primary_key)]
+    pub id: ChannelMemberId,
+    pub channel_id: ChannelId,
+    pub user_id: UserId,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::channel::Entity",
+        from = "Column::ChannelId",
+        to = "super::channel::Column::Id"
+    )]
+    Channel,
+    #[sea_orm(
+        belongs_to = "super::user::Entity",
+        from = "Column::UserId",
+        to = "super::user::Column::Id"
+    )]
+    User,
+}
+
+impl Related<super::channel::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Channel.def()
+    }
+}
+
+impl Related<super::user::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::User.def()
+    }
+}
+
+#[derive(Debug)]
+pub struct UserToChannel;
+
+impl Linked for UserToChannel {
+    type FromEntity = super::user::Entity;
+
+    type ToEntity = super::channel::Entity;
+
+    fn link(&self) -> Vec<RelationDef> {
+        vec![
+            channel_member::Relation::User.def().rev(),
+            channel_member::Relation::Channel.def(),
+        ]
+    }
+}

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

@@ -0,0 +1,13 @@
+use super::ChannelId;
+use sea_orm::entity::prelude::*;
+
+#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "channel_parents")]
+pub struct Model {
+    #[sea_orm(primary_key)]
+    pub child_id: ChannelId,
+    #[sea_orm(primary_key)]
+    pub parent_id: ChannelId,
+}
+
+impl ActiveModelBehavior for ActiveModel {}

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

@@ -37,4 +37,10 @@ impl Related<super::follower::Entity> for Entity {
     }
 }
 
+impl Related<super::channel::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Follower.def()
+    }
+}
+
 impl ActiveModelBehavior for ActiveModel {}

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

@@ -26,6 +26,8 @@ pub enum Relation {
     RoomParticipant,
     #[sea_orm(has_many = "super::project::Entity")]
     HostedProjects,
+    #[sea_orm(has_many = "super::channel_member::Entity")]
+    ChannelMemberships,
 }
 
 impl Related<super::access_token::Entity> for Entity {
@@ -46,4 +48,10 @@ impl Related<super::project::Entity> for Entity {
     }
 }
 
+impl Related<super::channel_member::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::ChannelMemberships.def()
+    }
+}
+
 impl ActiveModelBehavior for ActiveModel {}

crates/collab_ui/src/panel.rs 🔗

@@ -17,10 +17,7 @@ use gpui::{
         Canvas, ChildView, Empty, Flex, Image, Label, List, ListOffset, ListState,
         MouseEventHandler, Orientation, Padding, ParentElement, Stack, Svg,
     },
-    geometry::{
-        rect::RectF,
-        vector::vec2f,
-    },
+    geometry::{rect::RectF, vector::vec2f},
     platform::{CursorStyle, MouseButton, PromptLevel},
     serde_json, AnyElement, AppContext, AsyncAppContext, Element, Entity, ModelHandle,
     Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
@@ -1452,11 +1449,8 @@ impl View for CollabPanel {
                 .with_child(ChildView::new(&self.context_menu, cx))
                 .into_any()
         })
-        .on_click(MouseButton::Left, |_, v, cx| {
-            cx.focus_self()
-        })
+        .on_click(MouseButton::Left, |_, _, cx| cx.focus_self())
         .into_any_named("channels panel")
-
     }
 }