diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index c690b6148ad120c41f9441bb95b5911b7e83e0ca..a446f6b44025da5d3dda67cdf9c94ca5bb092697 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/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"); diff --git a/crates/collab/migrations/20230727150500_add_channels.sql b/crates/collab/migrations/20230727150500_add_channels.sql new file mode 100644 index 0000000000000000000000000000000000000000..a62eb0aaaf1f66a0ca8294aac2160a98896a1e79 --- /dev/null +++ b/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"); diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index e16fa9edb1efed6167556194acadbddd52763a07..ca7227917ca0e4efdc526b3d07cd918a8766cde4 100644 --- a/crates/collab/src/db.rs +++ b/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> { + 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(&self, f: F) -> Result 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); diff --git a/crates/collab/src/db/channel.rs b/crates/collab/src/db/channel.rs new file mode 100644 index 0000000000000000000000000000000000000000..ebf5c26ac87a3ccf9d380aa38a040a2ea6c78d9a --- /dev/null +++ b/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, + // 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 for Entity { + fn to() -> RelationDef { + Relation::Member.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Room.def() + } +} + +// impl Related for Entity { +// fn to() -> RelationDef { +// Relation::Follower.def() +// } +// } diff --git a/crates/collab/src/db/channel_member.rs b/crates/collab/src/db/channel_member.rs new file mode 100644 index 0000000000000000000000000000000000000000..cad7f3853d403a9fe4311f090e7d2d1f57386ef7 --- /dev/null +++ b/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 for Entity { + fn to() -> RelationDef { + Relation::Channel.def() + } +} + +impl Related 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 { + vec![ + channel_member::Relation::User.def().rev(), + channel_member::Relation::Channel.def(), + ] + } +} diff --git a/crates/collab/src/db/channel_parent.rs b/crates/collab/src/db/channel_parent.rs new file mode 100644 index 0000000000000000000000000000000000000000..bf6cb447113e2afc591129020b47ab8cb903adf0 --- /dev/null +++ b/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 {} diff --git a/crates/collab/src/db/room.rs b/crates/collab/src/db/room.rs index c3e88670ebe00afa523b008e6bf669de01ec1d1e..c838d1273b5a41751a7dbd6226725f7067c7abff 100644 --- a/crates/collab/src/db/room.rs +++ b/crates/collab/src/db/room.rs @@ -37,4 +37,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Follower.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/db/user.rs b/crates/collab/src/db/user.rs index c2b157bd0a758880fd6fe64b079fa8760b59df5c..2d0e2fdf0b2e2c87be734fe9bbce4c38d1e80b01 100644 --- a/crates/collab/src/db/user.rs +++ b/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 for Entity { @@ -46,4 +48,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::ChannelMemberships.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab_ui/src/panel.rs b/crates/collab_ui/src/panel.rs index bc79694d53eb04b86be14f3ae737e1a8bc6ef6ff..bdeac59af9cbfefe6204df44ce922d2bbf3a5f0e 100644 --- a/crates/collab_ui/src/panel.rs +++ b/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") - } }