Get integration tests passing with sqlite

Max Brunsfeld and Antonio Scandurra created

Co-authored-by: Antonio Scandurra <antonio@zed.dev>

Change summary

crates/client/src/channel.rs                                   |  820 ---
crates/client/src/client.rs                                    |    2 
crates/collab/migrations.sqlite/20221109000000_test_schema.sql |  108 
crates/collab/src/api.rs                                       |  103 
crates/collab/src/db.rs                                        | 1025 +--
crates/collab/src/db_tests.rs                                  |  434 -
crates/collab/src/integration_tests.rs                         |  551 -
crates/collab/src/main.rs                                      |    4 
crates/collab/src/rpc.rs                                       |  324 -
crates/collab/src/rpc/store.rs                                 |  131 
crates/zed/src/main.rs                                         |    1 
11 files changed, 467 insertions(+), 3,036 deletions(-)

Detailed changes

crates/client/src/channel.rs 🔗

@@ -1,820 +0,0 @@
-use super::{
-    proto,
-    user::{User, UserStore},
-    Client, Status, Subscription, TypedEnvelope,
-};
-use anyhow::{anyhow, Context, Result};
-use futures::lock::Mutex;
-use gpui::{
-    AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, WeakModelHandle,
-};
-use postage::prelude::Stream;
-use rand::prelude::*;
-use std::{
-    collections::{HashMap, HashSet},
-    mem,
-    ops::Range,
-    sync::Arc,
-};
-use sum_tree::{Bias, SumTree};
-use time::OffsetDateTime;
-use util::{post_inc, ResultExt as _, TryFutureExt};
-
-pub struct ChannelList {
-    available_channels: Option<Vec<ChannelDetails>>,
-    channels: HashMap<u64, WeakModelHandle<Channel>>,
-    client: Arc<Client>,
-    user_store: ModelHandle<UserStore>,
-    _task: Task<Option<()>>,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct ChannelDetails {
-    pub id: u64,
-    pub name: String,
-}
-
-pub struct Channel {
-    details: ChannelDetails,
-    messages: SumTree<ChannelMessage>,
-    loaded_all_messages: bool,
-    next_pending_message_id: usize,
-    user_store: ModelHandle<UserStore>,
-    rpc: Arc<Client>,
-    outgoing_messages_lock: Arc<Mutex<()>>,
-    rng: StdRng,
-    _subscription: Subscription,
-}
-
-#[derive(Clone, Debug)]
-pub struct ChannelMessage {
-    pub id: ChannelMessageId,
-    pub body: String,
-    pub timestamp: OffsetDateTime,
-    pub sender: Arc<User>,
-    pub nonce: u128,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum ChannelMessageId {
-    Saved(u64),
-    Pending(usize),
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct ChannelMessageSummary {
-    max_id: ChannelMessageId,
-    count: usize,
-}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
-struct Count(usize);
-
-pub enum ChannelListEvent {}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum ChannelEvent {
-    MessagesUpdated {
-        old_range: Range<usize>,
-        new_count: usize,
-    },
-}
-
-impl Entity for ChannelList {
-    type Event = ChannelListEvent;
-}
-
-impl ChannelList {
-    pub fn new(
-        user_store: ModelHandle<UserStore>,
-        rpc: Arc<Client>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let _task = cx.spawn_weak(|this, mut cx| {
-            let rpc = rpc.clone();
-            async move {
-                let mut status = rpc.status();
-                while let Some((status, this)) = status.recv().await.zip(this.upgrade(&cx)) {
-                    match status {
-                        Status::Connected { .. } => {
-                            let response = rpc
-                                .request(proto::GetChannels {})
-                                .await
-                                .context("failed to fetch available channels")?;
-                            this.update(&mut cx, |this, cx| {
-                                this.available_channels =
-                                    Some(response.channels.into_iter().map(Into::into).collect());
-
-                                let mut to_remove = Vec::new();
-                                for (channel_id, channel) in &this.channels {
-                                    if let Some(channel) = channel.upgrade(cx) {
-                                        channel.update(cx, |channel, cx| channel.rejoin(cx))
-                                    } else {
-                                        to_remove.push(*channel_id);
-                                    }
-                                }
-
-                                for channel_id in to_remove {
-                                    this.channels.remove(&channel_id);
-                                }
-                                cx.notify();
-                            });
-                        }
-                        Status::SignedOut { .. } => {
-                            this.update(&mut cx, |this, cx| {
-                                this.available_channels = None;
-                                this.channels.clear();
-                                cx.notify();
-                            });
-                        }
-                        _ => {}
-                    }
-                }
-                Ok(())
-            }
-            .log_err()
-        });
-
-        Self {
-            available_channels: None,
-            channels: Default::default(),
-            user_store,
-            client: rpc,
-            _task,
-        }
-    }
-
-    pub fn available_channels(&self) -> Option<&[ChannelDetails]> {
-        self.available_channels.as_deref()
-    }
-
-    pub fn get_channel(
-        &mut self,
-        id: u64,
-        cx: &mut MutableAppContext,
-    ) -> Option<ModelHandle<Channel>> {
-        if let Some(channel) = self.channels.get(&id).and_then(|c| c.upgrade(cx)) {
-            return Some(channel);
-        }
-
-        let channels = self.available_channels.as_ref()?;
-        let details = channels.iter().find(|details| details.id == id)?.clone();
-        let channel = cx.add_model(|cx| {
-            Channel::new(details, self.user_store.clone(), self.client.clone(), cx)
-        });
-        self.channels.insert(id, channel.downgrade());
-        Some(channel)
-    }
-}
-
-impl Entity for Channel {
-    type Event = ChannelEvent;
-
-    fn release(&mut self, _: &mut MutableAppContext) {
-        self.rpc
-            .send(proto::LeaveChannel {
-                channel_id: self.details.id,
-            })
-            .log_err();
-    }
-}
-
-impl Channel {
-    pub fn init(rpc: &Arc<Client>) {
-        rpc.add_model_message_handler(Self::handle_message_sent);
-    }
-
-    pub fn new(
-        details: ChannelDetails,
-        user_store: ModelHandle<UserStore>,
-        rpc: Arc<Client>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let _subscription = rpc.add_model_for_remote_entity(details.id, cx);
-
-        {
-            let user_store = user_store.clone();
-            let rpc = rpc.clone();
-            let channel_id = details.id;
-            cx.spawn(|channel, mut cx| {
-                async move {
-                    let response = rpc.request(proto::JoinChannel { channel_id }).await?;
-                    let messages =
-                        messages_from_proto(response.messages, &user_store, &mut cx).await?;
-                    let loaded_all_messages = response.done;
-
-                    channel.update(&mut cx, |channel, cx| {
-                        channel.insert_messages(messages, cx);
-                        channel.loaded_all_messages = loaded_all_messages;
-                    });
-
-                    Ok(())
-                }
-                .log_err()
-            })
-            .detach();
-        }
-
-        Self {
-            details,
-            user_store,
-            rpc,
-            outgoing_messages_lock: Default::default(),
-            messages: Default::default(),
-            loaded_all_messages: false,
-            next_pending_message_id: 0,
-            rng: StdRng::from_entropy(),
-            _subscription,
-        }
-    }
-
-    pub fn name(&self) -> &str {
-        &self.details.name
-    }
-
-    pub fn send_message(
-        &mut self,
-        body: String,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<Task<Result<()>>> {
-        if body.is_empty() {
-            Err(anyhow!("message body can't be empty"))?;
-        }
-
-        let current_user = self
-            .user_store
-            .read(cx)
-            .current_user()
-            .ok_or_else(|| anyhow!("current_user is not present"))?;
-
-        let channel_id = self.details.id;
-        let pending_id = ChannelMessageId::Pending(post_inc(&mut self.next_pending_message_id));
-        let nonce = self.rng.gen();
-        self.insert_messages(
-            SumTree::from_item(
-                ChannelMessage {
-                    id: pending_id,
-                    body: body.clone(),
-                    sender: current_user,
-                    timestamp: OffsetDateTime::now_utc(),
-                    nonce,
-                },
-                &(),
-            ),
-            cx,
-        );
-        let user_store = self.user_store.clone();
-        let rpc = self.rpc.clone();
-        let outgoing_messages_lock = self.outgoing_messages_lock.clone();
-        Ok(cx.spawn(|this, mut cx| async move {
-            let outgoing_message_guard = outgoing_messages_lock.lock().await;
-            let request = rpc.request(proto::SendChannelMessage {
-                channel_id,
-                body,
-                nonce: Some(nonce.into()),
-            });
-            let response = request.await?;
-            drop(outgoing_message_guard);
-            let message = ChannelMessage::from_proto(
-                response.message.ok_or_else(|| anyhow!("invalid message"))?,
-                &user_store,
-                &mut cx,
-            )
-            .await?;
-            this.update(&mut cx, |this, cx| {
-                this.insert_messages(SumTree::from_item(message, &()), cx);
-                Ok(())
-            })
-        }))
-    }
-
-    pub fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) -> bool {
-        if !self.loaded_all_messages {
-            let rpc = self.rpc.clone();
-            let user_store = self.user_store.clone();
-            let channel_id = self.details.id;
-            if let Some(before_message_id) =
-                self.messages.first().and_then(|message| match message.id {
-                    ChannelMessageId::Saved(id) => Some(id),
-                    ChannelMessageId::Pending(_) => None,
-                })
-            {
-                cx.spawn(|this, mut cx| {
-                    async move {
-                        let response = rpc
-                            .request(proto::GetChannelMessages {
-                                channel_id,
-                                before_message_id,
-                            })
-                            .await?;
-                        let loaded_all_messages = response.done;
-                        let messages =
-                            messages_from_proto(response.messages, &user_store, &mut cx).await?;
-                        this.update(&mut cx, |this, cx| {
-                            this.loaded_all_messages = loaded_all_messages;
-                            this.insert_messages(messages, cx);
-                        });
-                        Ok(())
-                    }
-                    .log_err()
-                })
-                .detach();
-                return true;
-            }
-        }
-        false
-    }
-
-    pub fn rejoin(&mut self, cx: &mut ModelContext<Self>) {
-        let user_store = self.user_store.clone();
-        let rpc = self.rpc.clone();
-        let channel_id = self.details.id;
-        cx.spawn(|this, mut cx| {
-            async move {
-                let response = rpc.request(proto::JoinChannel { channel_id }).await?;
-                let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
-                let loaded_all_messages = response.done;
-
-                let pending_messages = this.update(&mut cx, |this, cx| {
-                    if let Some((first_new_message, last_old_message)) =
-                        messages.first().zip(this.messages.last())
-                    {
-                        if first_new_message.id > last_old_message.id {
-                            let old_messages = mem::take(&mut this.messages);
-                            cx.emit(ChannelEvent::MessagesUpdated {
-                                old_range: 0..old_messages.summary().count,
-                                new_count: 0,
-                            });
-                            this.loaded_all_messages = loaded_all_messages;
-                        }
-                    }
-
-                    this.insert_messages(messages, cx);
-                    if loaded_all_messages {
-                        this.loaded_all_messages = loaded_all_messages;
-                    }
-
-                    this.pending_messages().cloned().collect::<Vec<_>>()
-                });
-
-                for pending_message in pending_messages {
-                    let request = rpc.request(proto::SendChannelMessage {
-                        channel_id,
-                        body: pending_message.body,
-                        nonce: Some(pending_message.nonce.into()),
-                    });
-                    let response = request.await?;
-                    let message = ChannelMessage::from_proto(
-                        response.message.ok_or_else(|| anyhow!("invalid message"))?,
-                        &user_store,
-                        &mut cx,
-                    )
-                    .await?;
-                    this.update(&mut cx, |this, cx| {
-                        this.insert_messages(SumTree::from_item(message, &()), cx);
-                    });
-                }
-
-                Ok(())
-            }
-            .log_err()
-        })
-        .detach();
-    }
-
-    pub fn message_count(&self) -> usize {
-        self.messages.summary().count
-    }
-
-    pub fn messages(&self) -> &SumTree<ChannelMessage> {
-        &self.messages
-    }
-
-    pub fn message(&self, ix: usize) -> &ChannelMessage {
-        let mut cursor = self.messages.cursor::<Count>();
-        cursor.seek(&Count(ix), Bias::Right, &());
-        cursor.item().unwrap()
-    }
-
-    pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
-        let mut cursor = self.messages.cursor::<Count>();
-        cursor.seek(&Count(range.start), Bias::Right, &());
-        cursor.take(range.len())
-    }
-
-    pub fn pending_messages(&self) -> impl Iterator<Item = &ChannelMessage> {
-        let mut cursor = self.messages.cursor::<ChannelMessageId>();
-        cursor.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
-        cursor
-    }
-
-    async fn handle_message_sent(
-        this: ModelHandle<Self>,
-        message: TypedEnvelope<proto::ChannelMessageSent>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
-        let message = message
-            .payload
-            .message
-            .ok_or_else(|| anyhow!("empty message"))?;
-
-        let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?;
-        this.update(&mut cx, |this, cx| {
-            this.insert_messages(SumTree::from_item(message, &()), cx)
-        });
-
-        Ok(())
-    }
-
-    fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
-        if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
-            let nonces = messages
-                .cursor::<()>()
-                .map(|m| m.nonce)
-                .collect::<HashSet<_>>();
-
-            let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>();
-            let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
-            let start_ix = old_cursor.start().1 .0;
-            let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
-            let removed_count = removed_messages.summary().count;
-            let new_count = messages.summary().count;
-            let end_ix = start_ix + removed_count;
-
-            new_messages.push_tree(messages, &());
-
-            let mut ranges = Vec::<Range<usize>>::new();
-            if new_messages.last().unwrap().is_pending() {
-                new_messages.push_tree(old_cursor.suffix(&()), &());
-            } else {
-                new_messages.push_tree(
-                    old_cursor.slice(&ChannelMessageId::Pending(0), Bias::Left, &()),
-                    &(),
-                );
-
-                while let Some(message) = old_cursor.item() {
-                    let message_ix = old_cursor.start().1 .0;
-                    if nonces.contains(&message.nonce) {
-                        if ranges.last().map_or(false, |r| r.end == message_ix) {
-                            ranges.last_mut().unwrap().end += 1;
-                        } else {
-                            ranges.push(message_ix..message_ix + 1);
-                        }
-                    } else {
-                        new_messages.push(message.clone(), &());
-                    }
-                    old_cursor.next(&());
-                }
-            }
-
-            drop(old_cursor);
-            self.messages = new_messages;
-
-            for range in ranges.into_iter().rev() {
-                cx.emit(ChannelEvent::MessagesUpdated {
-                    old_range: range,
-                    new_count: 0,
-                });
-            }
-            cx.emit(ChannelEvent::MessagesUpdated {
-                old_range: start_ix..end_ix,
-                new_count,
-            });
-            cx.notify();
-        }
-    }
-}
-
-async fn messages_from_proto(
-    proto_messages: Vec<proto::ChannelMessage>,
-    user_store: &ModelHandle<UserStore>,
-    cx: &mut AsyncAppContext,
-) -> Result<SumTree<ChannelMessage>> {
-    let unique_user_ids = proto_messages
-        .iter()
-        .map(|m| m.sender_id)
-        .collect::<HashSet<_>>()
-        .into_iter()
-        .collect();
-    user_store
-        .update(cx, |user_store, cx| {
-            user_store.get_users(unique_user_ids, cx)
-        })
-        .await?;
-
-    let mut messages = Vec::with_capacity(proto_messages.len());
-    for message in proto_messages {
-        messages.push(ChannelMessage::from_proto(message, user_store, cx).await?);
-    }
-    let mut result = SumTree::new();
-    result.extend(messages, &());
-    Ok(result)
-}
-
-impl From<proto::Channel> for ChannelDetails {
-    fn from(message: proto::Channel) -> Self {
-        Self {
-            id: message.id,
-            name: message.name,
-        }
-    }
-}
-
-impl ChannelMessage {
-    pub async fn from_proto(
-        message: proto::ChannelMessage,
-        user_store: &ModelHandle<UserStore>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Self> {
-        let sender = user_store
-            .update(cx, |user_store, cx| {
-                user_store.get_user(message.sender_id, cx)
-            })
-            .await?;
-        Ok(ChannelMessage {
-            id: ChannelMessageId::Saved(message.id),
-            body: message.body,
-            timestamp: OffsetDateTime::from_unix_timestamp(message.timestamp as i64)?,
-            sender,
-            nonce: message
-                .nonce
-                .ok_or_else(|| anyhow!("nonce is required"))?
-                .into(),
-        })
-    }
-
-    pub fn is_pending(&self) -> bool {
-        matches!(self.id, ChannelMessageId::Pending(_))
-    }
-}
-
-impl sum_tree::Item for ChannelMessage {
-    type Summary = ChannelMessageSummary;
-
-    fn summary(&self) -> Self::Summary {
-        ChannelMessageSummary {
-            max_id: self.id,
-            count: 1,
-        }
-    }
-}
-
-impl Default for ChannelMessageId {
-    fn default() -> Self {
-        Self::Saved(0)
-    }
-}
-
-impl sum_tree::Summary for ChannelMessageSummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &()) {
-        self.max_id = summary.max_id;
-        self.count += summary.count;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
-    fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
-        debug_assert!(summary.max_id > *self);
-        *self = summary.max_id;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count {
-    fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
-        self.0 += summary.count;
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::test::{FakeHttpClient, FakeServer};
-    use gpui::TestAppContext;
-
-    #[gpui::test]
-    async fn test_channel_messages(cx: &mut TestAppContext) {
-        cx.foreground().forbid_parking();
-
-        let user_id = 5;
-        let http_client = FakeHttpClient::with_404_response();
-        let client = cx.update(|cx| Client::new(http_client.clone(), cx));
-        let server = FakeServer::for_client(user_id, &client, cx).await;
-
-        Channel::init(&client);
-        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-
-        let channel_list = cx.add_model(|cx| ChannelList::new(user_store, client.clone(), cx));
-        channel_list.read_with(cx, |list, _| assert_eq!(list.available_channels(), None));
-
-        // Get the available channels.
-        let get_channels = server.receive::<proto::GetChannels>().await.unwrap();
-        server
-            .respond(
-                get_channels.receipt(),
-                proto::GetChannelsResponse {
-                    channels: vec![proto::Channel {
-                        id: 5,
-                        name: "the-channel".to_string(),
-                    }],
-                },
-            )
-            .await;
-        channel_list.next_notification(cx).await;
-        channel_list.read_with(cx, |list, _| {
-            assert_eq!(
-                list.available_channels().unwrap(),
-                &[ChannelDetails {
-                    id: 5,
-                    name: "the-channel".into(),
-                }]
-            )
-        });
-
-        let get_users = server.receive::<proto::GetUsers>().await.unwrap();
-        assert_eq!(get_users.payload.user_ids, vec![5]);
-        server
-            .respond(
-                get_users.receipt(),
-                proto::UsersResponse {
-                    users: vec![proto::User {
-                        id: 5,
-                        github_login: "nathansobo".into(),
-                        avatar_url: "http://avatar.com/nathansobo".into(),
-                    }],
-                },
-            )
-            .await;
-
-        // Join a channel and populate its existing messages.
-        let channel = channel_list
-            .update(cx, |list, cx| {
-                let channel_id = list.available_channels().unwrap()[0].id;
-                list.get_channel(channel_id, cx)
-            })
-            .unwrap();
-        channel.read_with(cx, |channel, _| assert!(channel.messages().is_empty()));
-        let join_channel = server.receive::<proto::JoinChannel>().await.unwrap();
-        server
-            .respond(
-                join_channel.receipt(),
-                proto::JoinChannelResponse {
-                    messages: vec![
-                        proto::ChannelMessage {
-                            id: 10,
-                            body: "a".into(),
-                            timestamp: 1000,
-                            sender_id: 5,
-                            nonce: Some(1.into()),
-                        },
-                        proto::ChannelMessage {
-                            id: 11,
-                            body: "b".into(),
-                            timestamp: 1001,
-                            sender_id: 6,
-                            nonce: Some(2.into()),
-                        },
-                    ],
-                    done: false,
-                },
-            )
-            .await;
-
-        // Client requests all users for the received messages
-        let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
-        get_users.payload.user_ids.sort();
-        assert_eq!(get_users.payload.user_ids, vec![6]);
-        server
-            .respond(
-                get_users.receipt(),
-                proto::UsersResponse {
-                    users: vec![proto::User {
-                        id: 6,
-                        github_login: "maxbrunsfeld".into(),
-                        avatar_url: "http://avatar.com/maxbrunsfeld".into(),
-                    }],
-                },
-            )
-            .await;
-
-        assert_eq!(
-            channel.next_event(cx).await,
-            ChannelEvent::MessagesUpdated {
-                old_range: 0..0,
-                new_count: 2,
-            }
-        );
-        channel.read_with(cx, |channel, _| {
-            assert_eq!(
-                channel
-                    .messages_in_range(0..2)
-                    .map(|message| (message.sender.github_login.clone(), message.body.clone()))
-                    .collect::<Vec<_>>(),
-                &[
-                    ("nathansobo".into(), "a".into()),
-                    ("maxbrunsfeld".into(), "b".into())
-                ]
-            );
-        });
-
-        // Receive a new message.
-        server.send(proto::ChannelMessageSent {
-            channel_id: channel.read_with(cx, |channel, _| channel.details.id),
-            message: Some(proto::ChannelMessage {
-                id: 12,
-                body: "c".into(),
-                timestamp: 1002,
-                sender_id: 7,
-                nonce: Some(3.into()),
-            }),
-        });
-
-        // Client requests user for message since they haven't seen them yet
-        let get_users = server.receive::<proto::GetUsers>().await.unwrap();
-        assert_eq!(get_users.payload.user_ids, vec![7]);
-        server
-            .respond(
-                get_users.receipt(),
-                proto::UsersResponse {
-                    users: vec![proto::User {
-                        id: 7,
-                        github_login: "as-cii".into(),
-                        avatar_url: "http://avatar.com/as-cii".into(),
-                    }],
-                },
-            )
-            .await;
-
-        assert_eq!(
-            channel.next_event(cx).await,
-            ChannelEvent::MessagesUpdated {
-                old_range: 2..2,
-                new_count: 1,
-            }
-        );
-        channel.read_with(cx, |channel, _| {
-            assert_eq!(
-                channel
-                    .messages_in_range(2..3)
-                    .map(|message| (message.sender.github_login.clone(), message.body.clone()))
-                    .collect::<Vec<_>>(),
-                &[("as-cii".into(), "c".into())]
-            )
-        });
-
-        // Scroll up to view older messages.
-        channel.update(cx, |channel, cx| {
-            assert!(channel.load_more_messages(cx));
-        });
-        let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
-        assert_eq!(get_messages.payload.channel_id, 5);
-        assert_eq!(get_messages.payload.before_message_id, 10);
-        server
-            .respond(
-                get_messages.receipt(),
-                proto::GetChannelMessagesResponse {
-                    done: true,
-                    messages: vec![
-                        proto::ChannelMessage {
-                            id: 8,
-                            body: "y".into(),
-                            timestamp: 998,
-                            sender_id: 5,
-                            nonce: Some(4.into()),
-                        },
-                        proto::ChannelMessage {
-                            id: 9,
-                            body: "z".into(),
-                            timestamp: 999,
-                            sender_id: 6,
-                            nonce: Some(5.into()),
-                        },
-                    ],
-                },
-            )
-            .await;
-
-        assert_eq!(
-            channel.next_event(cx).await,
-            ChannelEvent::MessagesUpdated {
-                old_range: 0..0,
-                new_count: 2,
-            }
-        );
-        channel.read_with(cx, |channel, _| {
-            assert_eq!(
-                channel
-                    .messages_in_range(0..2)
-                    .map(|message| (message.sender.github_login.clone(), message.body.clone()))
-                    .collect::<Vec<_>>(),
-                &[
-                    ("nathansobo".into(), "y".into()),
-                    ("maxbrunsfeld".into(), "z".into())
-                ]
-            );
-        });
-    }
-}

crates/client/src/client.rs 🔗

@@ -1,7 +1,6 @@
 #[cfg(any(test, feature = "test-support"))]
 pub mod test;
 
-pub mod channel;
 pub mod http;
 pub mod telemetry;
 pub mod user;
@@ -44,7 +43,6 @@ use thiserror::Error;
 use url::Url;
 use util::{ResultExt, TryFutureExt};
 
-pub use channel::*;
 pub use rpc::*;
 pub use user::*;
 

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

@@ -1,19 +1,14 @@
-CREATE TABLE IF NOT EXISTS "sessions" (
-    "id" VARCHAR NOT NULL PRIMARY KEY,
-    "expires" TIMESTAMP WITH TIME ZONE NULL,
-    "session" TEXT NOT NULL
-);
-
 CREATE TABLE IF NOT EXISTS "users" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
+    "id" INTEGER PRIMARY KEY,
     "github_login" VARCHAR,
     "admin" BOOLEAN,
-    email_address VARCHAR(255) DEFAULT NULL,
-    invite_code VARCHAR(64),
-    invite_count INTEGER NOT NULL DEFAULT 0,
-    inviter_id INTEGER REFERENCES users (id),
-    connected_once BOOLEAN NOT NULL DEFAULT false,
-    created_at TIMESTAMP NOT NULL DEFAULT now,
+    "email_address" VARCHAR(255) DEFAULT NULL,
+    "invite_code" VARCHAR(64),
+    "invite_count" INTEGER NOT NULL DEFAULT 0,
+    "inviter_id" INTEGER REFERENCES users (id),
+    "connected_once" BOOLEAN NOT NULL DEFAULT false,
+    "created_at" TIMESTAMP NOT NULL DEFAULT now,
+    "metrics_id" VARCHAR(255),
     "github_user_id" INTEGER
 );
 CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
@@ -22,56 +17,14 @@ CREATE INDEX "index_users_on_email_address" ON "users" ("email_address");
 CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id");
 
 CREATE TABLE IF NOT EXISTS "access_tokens" (
-    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
+    "id" INTEGER PRIMARY KEY,
     "user_id" INTEGER REFERENCES users (id),
     "hash" VARCHAR(128)
 );
 CREATE INDEX "index_access_tokens_user_id" ON "access_tokens" ("user_id");
 
-CREATE TABLE IF NOT EXISTS "orgs" (
-    "id" SERIAL PRIMARY KEY,
-    "name" VARCHAR NOT NULL,
-    "slug" VARCHAR NOT NULL
-);
-CREATE UNIQUE INDEX "index_orgs_slug" ON "orgs" ("slug");
-
-CREATE TABLE IF NOT EXISTS "org_memberships" (
-    "id" SERIAL PRIMARY KEY,
-    "org_id" INTEGER REFERENCES orgs (id) NOT NULL,
-    "user_id" INTEGER REFERENCES users (id) NOT NULL,
-    "admin" BOOLEAN NOT NULL
-);
-CREATE INDEX "index_org_memberships_user_id" ON "org_memberships" ("user_id");
-CREATE UNIQUE INDEX "index_org_memberships_org_id_and_user_id" ON "org_memberships" ("org_id", "user_id");
-
-CREATE TABLE IF NOT EXISTS "channels" (
-    "id" SERIAL PRIMARY KEY,
-    "owner_id" INTEGER NOT NULL,
-    "owner_is_user" BOOLEAN NOT NULL,
-    "name" VARCHAR NOT NULL
-);
-CREATE UNIQUE INDEX "index_channels_owner_and_name" ON "channels" ("owner_is_user", "owner_id", "name");
-
-CREATE TABLE IF NOT EXISTS "channel_memberships" (
-    "id" SERIAL PRIMARY KEY,
-    "channel_id" INTEGER REFERENCES channels (id) NOT NULL,
-    "user_id" INTEGER REFERENCES users (id) NOT NULL,
-    "admin" BOOLEAN NOT NULL
-);
-CREATE INDEX "index_channel_memberships_user_id" ON "channel_memberships" ("user_id");
-CREATE UNIQUE INDEX "index_channel_memberships_channel_id_and_user_id" ON "channel_memberships" ("channel_id", "user_id");
-
-CREATE TABLE IF NOT EXISTS "channel_messages" (
-    "id" SERIAL PRIMARY KEY,
-    "channel_id" INTEGER REFERENCES channels (id) NOT NULL,
-    "sender_id" INTEGER REFERENCES users (id) NOT NULL,
-    "body" TEXT NOT NULL,
-    "sent_at" TIMESTAMP
-);
-CREATE INDEX "index_channel_messages_channel_id" ON "channel_messages" ("channel_id");
-
 CREATE TABLE IF NOT EXISTS "contacts" (
-    "id" SERIAL PRIMARY KEY,
+    "id" INTEGER PRIMARY KEY,
     "user_id_a" INTEGER REFERENCES users (id) NOT NULL,
     "user_id_b" INTEGER REFERENCES users (id) NOT NULL,
     "a_to_b" BOOLEAN NOT NULL,
@@ -82,46 +35,7 @@ CREATE UNIQUE INDEX "index_contacts_user_ids" ON "contacts" ("user_id_a", "user_
 CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b");
 
 CREATE TABLE IF NOT EXISTS "projects" (
-    "id" SERIAL PRIMARY KEY,
+    "id" INTEGER PRIMARY KEY,
     "host_user_id" INTEGER REFERENCES users (id) NOT NULL,
     "unregistered" BOOLEAN NOT NULL DEFAULT false
 );
-
-CREATE TABLE IF NOT EXISTS "worktree_extensions" (
-    "id" SERIAL PRIMARY KEY,
-    "project_id" INTEGER REFERENCES projects (id) NOT NULL,
-    "worktree_id" INTEGER NOT NULL,
-    "extension" VARCHAR(255),
-    "count" INTEGER NOT NULL
-);
-CREATE UNIQUE INDEX "index_worktree_extensions_on_project_id_and_worktree_id_and_extension" ON "worktree_extensions" ("project_id", "worktree_id", "extension");
-
-CREATE TABLE IF NOT EXISTS "project_activity_periods" (
-    "id" SERIAL PRIMARY KEY,
-    "duration_millis" INTEGER NOT NULL,
-    "ended_at" TIMESTAMP NOT NULL,
-    "user_id" INTEGER REFERENCES users (id) NOT NULL,
-    "project_id" INTEGER REFERENCES projects (id) NOT NULL
-);
-CREATE INDEX "index_project_activity_periods_on_ended_at" ON "project_activity_periods" ("ended_at");
-
-CREATE TABLE IF NOT EXISTS "signups" (
-    "id" SERIAL PRIMARY KEY,
-    "email_address" VARCHAR NOT NULL,
-    "email_confirmation_code" VARCHAR(64) NOT NULL,
-    "email_confirmation_sent" BOOLEAN NOT NULL,
-    "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    "device_id" VARCHAR,
-    "user_id" INTEGER REFERENCES users (id) ON DELETE CASCADE,
-    "inviting_user_id" INTEGER REFERENCES users (id) ON DELETE SET NULL,
-
-    "platform_mac" BOOLEAN NOT NULL,
-    "platform_linux" BOOLEAN NOT NULL,
-    "platform_windows" BOOLEAN NOT NULL,
-    "platform_unknown" BOOLEAN NOT NULL,
-
-    "editor_features" VARCHAR[],
-    "programming_languages" VARCHAR[]
-);
-CREATE UNIQUE INDEX "index_signups_on_email_address" ON "signups" ("email_address");
-CREATE INDEX "index_signups_on_email_confirmation_sent" ON "signups" ("email_confirmation_sent");

crates/collab/src/api.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     auth,
-    db::{Invite, NewUserParams, ProjectId, Signup, User, UserId, WaitlistSummary},
+    db::{Invite, NewUserParams, Signup, User, UserId, WaitlistSummary},
     rpc::{self, ResultExt},
     AppState, Error, Result,
 };
@@ -16,9 +16,7 @@ use axum::{
 };
 use axum_extra::response::ErasedJson;
 use serde::{Deserialize, Serialize};
-use serde_json::json;
-use std::{sync::Arc, time::Duration};
-use time::OffsetDateTime;
+use std::sync::Arc;
 use tower::ServiceBuilder;
 use tracing::instrument;
 
@@ -32,16 +30,6 @@ pub fn routes(rpc_server: Arc<rpc::Server>, state: Arc<AppState>) -> Router<Body
         .route("/invite_codes/:code", get(get_user_for_invite_code))
         .route("/panic", post(trace_panic))
         .route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
-        .route(
-            "/user_activity/summary",
-            get(get_top_users_activity_summary),
-        )
-        .route(
-            "/user_activity/timeline/:user_id",
-            get(get_user_activity_timeline),
-        )
-        .route("/user_activity/counts", get(get_active_user_counts))
-        .route("/project_metadata", get(get_project_metadata))
         .route("/signups", post(create_signup))
         .route("/signups_summary", get(get_waitlist_summary))
         .route("/user_invites", post(create_invite_from_code))
@@ -283,93 +271,6 @@ async fn get_rpc_server_snapshot(
     Ok(ErasedJson::pretty(rpc_server.snapshot().await))
 }
 
-#[derive(Deserialize)]
-struct TimePeriodParams {
-    #[serde(with = "time::serde::iso8601")]
-    start: OffsetDateTime,
-    #[serde(with = "time::serde::iso8601")]
-    end: OffsetDateTime,
-}
-
-async fn get_top_users_activity_summary(
-    Query(params): Query<TimePeriodParams>,
-    Extension(app): Extension<Arc<AppState>>,
-) -> Result<ErasedJson> {
-    let summary = app
-        .db
-        .get_top_users_activity_summary(params.start..params.end, 100)
-        .await?;
-    Ok(ErasedJson::pretty(summary))
-}
-
-async fn get_user_activity_timeline(
-    Path(user_id): Path<i32>,
-    Query(params): Query<TimePeriodParams>,
-    Extension(app): Extension<Arc<AppState>>,
-) -> Result<ErasedJson> {
-    let summary = app
-        .db
-        .get_user_activity_timeline(params.start..params.end, UserId(user_id))
-        .await?;
-    Ok(ErasedJson::pretty(summary))
-}
-
-#[derive(Deserialize)]
-struct ActiveUserCountParams {
-    #[serde(flatten)]
-    period: TimePeriodParams,
-    durations_in_minutes: String,
-    #[serde(default)]
-    only_collaborative: bool,
-}
-
-#[derive(Serialize)]
-struct ActiveUserSet {
-    active_time_in_minutes: u64,
-    user_count: usize,
-}
-
-async fn get_active_user_counts(
-    Query(params): Query<ActiveUserCountParams>,
-    Extension(app): Extension<Arc<AppState>>,
-) -> Result<ErasedJson> {
-    let durations_in_minutes = params.durations_in_minutes.split(',');
-    let mut user_sets = Vec::new();
-    for duration in durations_in_minutes {
-        let duration = duration
-            .parse()
-            .map_err(|_| anyhow!("invalid duration: {duration}"))?;
-        user_sets.push(ActiveUserSet {
-            active_time_in_minutes: duration,
-            user_count: app
-                .db
-                .get_active_user_count(
-                    params.period.start..params.period.end,
-                    Duration::from_secs(duration * 60),
-                    params.only_collaborative,
-                )
-                .await?,
-        })
-    }
-    Ok(ErasedJson::pretty(user_sets))
-}
-
-#[derive(Deserialize)]
-struct GetProjectMetadataParams {
-    project_id: u64,
-}
-
-async fn get_project_metadata(
-    Query(params): Query<GetProjectMetadataParams>,
-    Extension(app): Extension<Arc<AppState>>,
-) -> Result<ErasedJson> {
-    let extensions = app
-        .db
-        .get_project_extensions(ProjectId::from_proto(params.project_id))
-        .await?;
-    Ok(ErasedJson::pretty(json!({ "extensions": extensions })))
-}
-
 #[derive(Deserialize)]
 struct CreateAccessTokenQueryParams {
     public_key: String,

crates/collab/src/db.rs 🔗

@@ -7,9 +7,9 @@ use serde::{Deserialize, Serialize};
 use sqlx::{
     migrate::{Migrate as _, Migration, MigrationSource},
     types::Uuid,
-    FromRow, QueryBuilder,
+    FromRow,
 };
-use std::{cmp, ops::Range, path::Path, time::Duration};
+use std::{path::Path, time::Duration};
 use time::{OffsetDateTime, PrimitiveDateTime};
 
 #[cfg(test)]
@@ -58,8 +58,8 @@ impl RowsAffected for sqlx::postgres::PgQueryResult {
     }
 }
 
+#[cfg(test)]
 impl Db<sqlx::Sqlite> {
-    #[cfg(test)]
     pub async fn new(url: &str, max_connections: u32) -> Result<Self> {
         let pool = sqlx::sqlite::SqlitePoolOptions::new()
             .max_connections(max_connections)
@@ -70,6 +70,76 @@ impl Db<sqlx::Sqlite> {
             background: None,
         })
     }
+
+    pub async fn get_user_metrics_id(&self, id: UserId) -> Result<String> {
+        test_support!(self, {
+            let query = "
+                SELECT metrics_id
+                FROM users
+                WHERE id = $1
+            ";
+            Ok(sqlx::query_scalar(query)
+                .bind(id)
+                .fetch_one(&self.pool)
+                .await?)
+        })
+    }
+
+    pub async fn create_user(
+        &self,
+        email_address: &str,
+        admin: bool,
+        params: NewUserParams,
+    ) -> Result<NewUserResult> {
+        test_support!(self, {
+            let query = "
+                INSERT INTO users (email_address, github_login, github_user_id, admin, metrics_id)
+                VALUES ($1, $2, $3, $4, $5)
+                ON CONFLICT (github_login) DO UPDATE SET github_login = excluded.github_login
+                RETURNING id, metrics_id
+            ";
+
+            let (user_id, metrics_id): (UserId, String) = sqlx::query_as(query)
+                .bind(email_address)
+                .bind(params.github_login)
+                .bind(params.github_user_id)
+                .bind(admin)
+                .bind(Uuid::new_v4().to_string())
+                .fetch_one(&self.pool)
+                .await?;
+            Ok(NewUserResult {
+                user_id,
+                metrics_id,
+                signup_device_id: None,
+                inviting_user_id: None,
+            })
+        })
+    }
+
+    pub async fn fuzzy_search_users(&self, _name_query: &str, _limit: u32) -> Result<Vec<User>> {
+        unimplemented!()
+    }
+
+    pub async fn create_user_from_invite(
+        &self,
+        _invite: &Invite,
+        _user: NewUserParams,
+    ) -> Result<Option<NewUserResult>> {
+        unimplemented!()
+    }
+
+    pub async fn create_signup(&self, _signup: Signup) -> Result<()> {
+        unimplemented!()
+    }
+
+    pub async fn create_invite_from_code(
+        &self,
+        _code: &str,
+        _email_address: &str,
+        _device_id: Option<&str>,
+    ) -> Result<Invite> {
+        unimplemented!()
+    }
 }
 
 impl Db<sqlx::Postgres> {
@@ -84,6 +154,302 @@ impl Db<sqlx::Postgres> {
             background: None,
         })
     }
+
+    pub async fn fuzzy_search_users(&self, name_query: &str, limit: u32) -> Result<Vec<User>> {
+        test_support!(self, {
+            let like_string = Self::fuzzy_like_string(name_query);
+            let query = "
+                SELECT users.*
+                FROM users
+                WHERE github_login ILIKE $1
+                ORDER BY github_login <-> $2
+                LIMIT $3
+            ";
+            Ok(sqlx::query_as(query)
+                .bind(like_string)
+                .bind(name_query)
+                .bind(limit as i32)
+                .fetch_all(&self.pool)
+                .await?)
+        })
+    }
+
+    pub async fn get_user_metrics_id(&self, id: UserId) -> Result<String> {
+        test_support!(self, {
+            let query = "
+                SELECT metrics_id::text
+                FROM users
+                WHERE id = $1
+            ";
+            Ok(sqlx::query_scalar(query)
+                .bind(id)
+                .fetch_one(&self.pool)
+                .await?)
+        })
+    }
+
+    pub async fn create_user(
+        &self,
+        email_address: &str,
+        admin: bool,
+        params: NewUserParams,
+    ) -> Result<NewUserResult> {
+        test_support!(self, {
+            let query = "
+                INSERT INTO users (email_address, github_login, github_user_id, admin)
+                VALUES ($1, $2, $3, $4)
+                ON CONFLICT (github_login) DO UPDATE SET github_login = excluded.github_login
+                RETURNING id, metrics_id::text
+            ";
+
+            let (user_id, metrics_id): (UserId, String) = sqlx::query_as(query)
+                .bind(email_address)
+                .bind(params.github_login)
+                .bind(params.github_user_id)
+                .bind(admin)
+                .fetch_one(&self.pool)
+                .await?;
+            Ok(NewUserResult {
+                user_id,
+                metrics_id,
+                signup_device_id: None,
+                inviting_user_id: None,
+            })
+        })
+    }
+
+    pub async fn create_user_from_invite(
+        &self,
+        invite: &Invite,
+        user: NewUserParams,
+    ) -> Result<Option<NewUserResult>> {
+        test_support!(self, {
+            let mut tx = self.pool.begin().await?;
+
+            let (signup_id, existing_user_id, inviting_user_id, signup_device_id): (
+                i32,
+                Option<UserId>,
+                Option<UserId>,
+                Option<String>,
+            ) = sqlx::query_as(
+                "
+                SELECT id, user_id, inviting_user_id, device_id
+                FROM signups
+                WHERE
+                    email_address = $1 AND
+                    email_confirmation_code = $2
+                ",
+            )
+            .bind(&invite.email_address)
+            .bind(&invite.email_confirmation_code)
+            .fetch_optional(&mut tx)
+            .await?
+            .ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "no such invite".to_string()))?;
+
+            if existing_user_id.is_some() {
+                return Ok(None);
+            }
+
+            let (user_id, metrics_id): (UserId, String) = sqlx::query_as(
+                "
+                INSERT INTO users
+                (email_address, github_login, github_user_id, admin, invite_count, invite_code)
+                VALUES
+                ($1, $2, $3, FALSE, $4, $5)
+                ON CONFLICT (github_login) DO UPDATE SET
+                    email_address = excluded.email_address,
+                    github_user_id = excluded.github_user_id,
+                    admin = excluded.admin
+                RETURNING id, metrics_id::text
+                ",
+            )
+            .bind(&invite.email_address)
+            .bind(&user.github_login)
+            .bind(&user.github_user_id)
+            .bind(&user.invite_count)
+            .bind(random_invite_code())
+            .fetch_one(&mut tx)
+            .await?;
+
+            sqlx::query(
+                "
+                UPDATE signups
+                SET user_id = $1
+                WHERE id = $2
+                ",
+            )
+            .bind(&user_id)
+            .bind(&signup_id)
+            .execute(&mut tx)
+            .await?;
+
+            if let Some(inviting_user_id) = inviting_user_id {
+                let id: Option<UserId> = sqlx::query_scalar(
+                    "
+                    UPDATE users
+                    SET invite_count = invite_count - 1
+                    WHERE id = $1 AND invite_count > 0
+                    RETURNING id
+                    ",
+                )
+                .bind(&inviting_user_id)
+                .fetch_optional(&mut tx)
+                .await?;
+
+                if id.is_none() {
+                    Err(Error::Http(
+                        StatusCode::UNAUTHORIZED,
+                        "no invites remaining".to_string(),
+                    ))?;
+                }
+
+                sqlx::query(
+                    "
+                    INSERT INTO contacts
+                        (user_id_a, user_id_b, a_to_b, should_notify, accepted)
+                    VALUES
+                        ($1, $2, TRUE, TRUE, TRUE)
+                    ON CONFLICT DO NOTHING
+                    ",
+                )
+                .bind(inviting_user_id)
+                .bind(user_id)
+                .execute(&mut tx)
+                .await?;
+            }
+
+            tx.commit().await?;
+            Ok(Some(NewUserResult {
+                user_id,
+                metrics_id,
+                inviting_user_id,
+                signup_device_id,
+            }))
+        })
+    }
+
+    pub async fn create_signup(&self, signup: Signup) -> Result<()> {
+        test_support!(self, {
+            sqlx::query(
+                "
+                INSERT INTO signups
+                (
+                    email_address,
+                    email_confirmation_code,
+                    email_confirmation_sent,
+                    platform_linux,
+                    platform_mac,
+                    platform_windows,
+                    platform_unknown,
+                    editor_features,
+                    programming_languages,
+                    device_id
+                )
+                VALUES
+                    ($1, $2, FALSE, $3, $4, $5, FALSE, $6)
+                RETURNING id
+                ",
+            )
+            .bind(&signup.email_address)
+            .bind(&random_email_confirmation_code())
+            .bind(&signup.platform_linux)
+            .bind(&signup.platform_mac)
+            .bind(&signup.platform_windows)
+            .bind(&signup.editor_features)
+            .bind(&signup.programming_languages)
+            .bind(&signup.device_id)
+            .execute(&self.pool)
+            .await?;
+            Ok(())
+        })
+    }
+
+    pub async fn create_invite_from_code(
+        &self,
+        code: &str,
+        email_address: &str,
+        device_id: Option<&str>,
+    ) -> Result<Invite> {
+        test_support!(self, {
+            let mut tx = self.pool.begin().await?;
+
+            let existing_user: Option<UserId> = sqlx::query_scalar(
+                "
+                SELECT id
+                FROM users
+                WHERE email_address = $1
+                ",
+            )
+            .bind(email_address)
+            .fetch_optional(&mut tx)
+            .await?;
+            if existing_user.is_some() {
+                Err(anyhow!("email address is already in use"))?;
+            }
+
+            let row: Option<(UserId, i32)> = sqlx::query_as(
+                "
+                SELECT id, invite_count
+                FROM users
+                WHERE invite_code = $1
+                ",
+            )
+            .bind(code)
+            .fetch_optional(&mut tx)
+            .await?;
+
+            let (inviter_id, invite_count) = match row {
+                Some(row) => row,
+                None => Err(Error::Http(
+                    StatusCode::NOT_FOUND,
+                    "invite code not found".to_string(),
+                ))?,
+            };
+
+            if invite_count == 0 {
+                Err(Error::Http(
+                    StatusCode::UNAUTHORIZED,
+                    "no invites remaining".to_string(),
+                ))?;
+            }
+
+            let email_confirmation_code: String = sqlx::query_scalar(
+                "
+                INSERT INTO signups
+                (
+                    email_address,
+                    email_confirmation_code,
+                    email_confirmation_sent,
+                    inviting_user_id,
+                    platform_linux,
+                    platform_mac,
+                    platform_windows,
+                    platform_unknown,
+                    device_id
+                )
+                VALUES
+                    ($1, $2, FALSE, $3, FALSE, FALSE, FALSE, TRUE, $4)
+                ON CONFLICT (email_address)
+                DO UPDATE SET
+                    inviting_user_id = excluded.inviting_user_id
+                RETURNING email_confirmation_code
+                ",
+            )
+            .bind(&email_address)
+            .bind(&random_email_confirmation_code())
+            .bind(&inviter_id)
+            .bind(&device_id)
+            .fetch_one(&mut tx)
+            .await?;
+
+            tx.commit().await?;
+
+            Ok(Invite {
+                email_address: email_address.into(),
+                email_confirmation_code,
+            })
+        })
+    }
 }
 
 impl<D> Db<D>
@@ -172,61 +538,12 @@ where
 
     // users
 
-    pub async fn create_user(
-        &self,
-        email_address: &str,
-        admin: bool,
-        params: NewUserParams,
-    ) -> Result<NewUserResult> {
-        test_support!(self, {
-            let query = "
-                INSERT INTO users (email_address, github_login, github_user_id, admin)
-                VALUES ($1, $2, $3, $4)
-                -- ON CONFLICT (github_login) DO UPDATE SET github_login = excluded.github_login
-                RETURNING id, 'the-metrics-id'
-            ";
-
-            let (user_id, metrics_id): (UserId, String) = sqlx::query_as(query)
-                .bind(email_address)
-                .bind(params.github_login)
-                .bind(params.github_user_id)
-                .bind(admin)
-                .fetch_one(&self.pool)
-                .await?;
-            Ok(NewUserResult {
-                user_id,
-                metrics_id,
-                signup_device_id: None,
-                inviting_user_id: None,
-            })
-        })
-    }
-
-    pub async fn get_all_users(&self, page: u32, limit: u32) -> Result<Vec<User>> {
-        test_support!(self, {
-            let query = "SELECT * FROM users ORDER BY github_login ASC LIMIT $1 OFFSET $2";
-            Ok(sqlx::query_as(query)
-                .bind(limit as i32)
-                .bind((page * limit) as i32)
-                .fetch_all(&self.pool)
-                .await?)
-        })
-    }
-
-    pub async fn fuzzy_search_users(&self, name_query: &str, limit: u32) -> Result<Vec<User>> {
-        test_support!(self, {
-            let like_string = Self::fuzzy_like_string(name_query);
-            let query = "
-                SELECT users.*
-                FROM users
-                WHERE github_login ILIKE $1
-                ORDER BY github_login <-> $2
-                LIMIT $3
-            ";
+    pub async fn get_all_users(&self, page: u32, limit: u32) -> Result<Vec<User>> {
+        test_support!(self, {
+            let query = "SELECT * FROM users ORDER BY github_login ASC LIMIT $1 OFFSET $2";
             Ok(sqlx::query_as(query)
-                .bind(like_string)
-                .bind(name_query)
                 .bind(limit as i32)
+                .bind((page * limit) as i32)
                 .fetch_all(&self.pool)
                 .await?)
         })
@@ -247,20 +564,6 @@ where
         })
     }
 
-    pub async fn get_user_metrics_id(&self, id: UserId) -> Result<String> {
-        test_support!(self, {
-            let query = "
-                SELECT metrics_id::text
-                FROM users
-                WHERE id = $1
-            ";
-            Ok(sqlx::query_scalar(query)
-                .bind(id)
-                .fetch_one(&self.pool)
-                .await?)
-        })
-    }
-
     pub async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<User>> {
         test_support!(self, {
             let query = "
@@ -389,42 +692,6 @@ where
 
     // signups
 
-    pub async fn create_signup(&self, signup: Signup) -> Result<()> {
-        test_support!(self, {
-            sqlx::query(
-                "
-                INSERT INTO signups
-                (
-                    email_address,
-                    email_confirmation_code,
-                    email_confirmation_sent,
-                    platform_linux,
-                    platform_mac,
-                    platform_windows,
-                    platform_unknown,
-                    editor_features,
-                    programming_languages,
-                    device_id
-                )
-                VALUES
-                    ($1, $2, FALSE, $3, $4, $5, FALSE, $6)
-                RETURNING id
-                ",
-            )
-            .bind(&signup.email_address)
-            .bind(&random_email_confirmation_code())
-            .bind(&signup.platform_linux)
-            .bind(&signup.platform_mac)
-            .bind(&signup.platform_windows)
-            // .bind(&signup.editor_features)
-            // .bind(&signup.programming_languages)
-            .bind(&signup.device_id)
-            .execute(&self.pool)
-            .await?;
-            Ok(())
-        })
-    }
-
     pub async fn get_waitlist_summary(&self) -> Result<WaitlistSummary> {
         test_support!(self, {
             Ok(sqlx::query_as(
@@ -487,116 +754,6 @@ where
         })
     }
 
-    pub async fn create_user_from_invite(
-        &self,
-        invite: &Invite,
-        user: NewUserParams,
-    ) -> Result<Option<NewUserResult>> {
-        test_support!(self, {
-            let mut tx = self.pool.begin().await?;
-
-            let (signup_id, existing_user_id, inviting_user_id, signup_device_id): (
-                i32,
-                Option<UserId>,
-                Option<UserId>,
-                Option<String>,
-            ) = sqlx::query_as(
-                "
-                SELECT id, user_id, inviting_user_id, device_id
-                FROM signups
-                WHERE
-                    email_address = $1 AND
-                    email_confirmation_code = $2
-                ",
-            )
-            .bind(&invite.email_address)
-            .bind(&invite.email_confirmation_code)
-            .fetch_optional(&mut tx)
-            .await?
-            .ok_or_else(|| Error::Http(StatusCode::NOT_FOUND, "no such invite".to_string()))?;
-
-            if existing_user_id.is_some() {
-                return Ok(None);
-            }
-
-            let (user_id, metrics_id): (UserId, String) = sqlx::query_as(
-                "
-                INSERT INTO users
-                (email_address, github_login, github_user_id, admin, invite_count, invite_code)
-                VALUES
-                ($1, $2, $3, FALSE, $4, $5)
-                ON CONFLICT (github_login) DO UPDATE SET
-                    email_address = excluded.email_address,
-                    github_user_id = excluded.github_user_id,
-                    admin = excluded.admin
-                RETURNING id, metrics_id::text
-                ",
-            )
-            .bind(&invite.email_address)
-            .bind(&user.github_login)
-            .bind(&user.github_user_id)
-            .bind(&user.invite_count)
-            .bind(random_invite_code())
-            .fetch_one(&mut tx)
-            .await?;
-
-            sqlx::query(
-                "
-                UPDATE signups
-                SET user_id = $1
-                WHERE id = $2
-                ",
-            )
-            .bind(&user_id)
-            .bind(&signup_id)
-            .execute(&mut tx)
-            .await?;
-
-            if let Some(inviting_user_id) = inviting_user_id {
-                let id: Option<UserId> = sqlx::query_scalar(
-                    "
-                    UPDATE users
-                    SET invite_count = invite_count - 1
-                    WHERE id = $1 AND invite_count > 0
-                    RETURNING id
-                    ",
-                )
-                .bind(&inviting_user_id)
-                .fetch_optional(&mut tx)
-                .await?;
-
-                if id.is_none() {
-                    Err(Error::Http(
-                        StatusCode::UNAUTHORIZED,
-                        "no invites remaining".to_string(),
-                    ))?;
-                }
-
-                sqlx::query(
-                    "
-                    INSERT INTO contacts
-                        (user_id_a, user_id_b, a_to_b, should_notify, accepted)
-                    VALUES
-                        ($1, $2, TRUE, TRUE, TRUE)
-                    ON CONFLICT DO NOTHING
-                    ",
-                )
-                .bind(inviting_user_id)
-                .bind(user_id)
-                .execute(&mut tx)
-                .await?;
-            }
-
-            tx.commit().await?;
-            Ok(Some(NewUserResult {
-                user_id,
-                metrics_id,
-                inviting_user_id,
-                signup_device_id,
-            }))
-        })
-    }
-
     // invite codes
 
     pub async fn set_invite_count_for_user(&self, id: UserId, count: u32) -> Result<()> {
@@ -664,98 +821,11 @@ where
             .bind(code)
             .fetch_optional(&self.pool)
             .await?
-            .ok_or_else(|| {
-                Error::Http(
-                    StatusCode::NOT_FOUND,
-                    "that invite code does not exist".to_string(),
-                )
-            })
-        })
-    }
-
-    pub async fn create_invite_from_code(
-        &self,
-        code: &str,
-        email_address: &str,
-        device_id: Option<&str>,
-    ) -> Result<Invite> {
-        test_support!(self, {
-            let mut tx = self.pool.begin().await?;
-
-            let existing_user: Option<UserId> = sqlx::query_scalar(
-                "
-                SELECT id
-                FROM users
-                WHERE email_address = $1
-                ",
-            )
-            .bind(email_address)
-            .fetch_optional(&mut tx)
-            .await?;
-            if existing_user.is_some() {
-                Err(anyhow!("email address is already in use"))?;
-            }
-
-            let row: Option<(UserId, i32)> = sqlx::query_as(
-                "
-                SELECT id, invite_count
-                FROM users
-                WHERE invite_code = $1
-                ",
-            )
-            .bind(code)
-            .fetch_optional(&mut tx)
-            .await?;
-
-            let (inviter_id, invite_count) = match row {
-                Some(row) => row,
-                None => Err(Error::Http(
-                    StatusCode::NOT_FOUND,
-                    "invite code not found".to_string(),
-                ))?,
-            };
-
-            if invite_count == 0 {
-                Err(Error::Http(
-                    StatusCode::UNAUTHORIZED,
-                    "no invites remaining".to_string(),
-                ))?;
-            }
-
-            let email_confirmation_code: String = sqlx::query_scalar(
-                "
-                INSERT INTO signups
-                (
-                    email_address,
-                    email_confirmation_code,
-                    email_confirmation_sent,
-                    inviting_user_id,
-                    platform_linux,
-                    platform_mac,
-                    platform_windows,
-                    platform_unknown,
-                    device_id
-                )
-                VALUES
-                    ($1, $2, FALSE, $3, FALSE, FALSE, FALSE, TRUE, $4)
-                ON CONFLICT (email_address)
-                DO UPDATE SET
-                    inviting_user_id = excluded.inviting_user_id
-                RETURNING email_confirmation_code
-                ",
-            )
-            .bind(&email_address)
-            .bind(&random_email_confirmation_code())
-            .bind(&inviter_id)
-            .bind(&device_id)
-            .fetch_one(&mut tx)
-            .await?;
-
-            tx.commit().await?;
-
-            Ok(Invite {
-                email_address: email_address.into(),
-                email_confirmation_code,
+            .ok_or_else(|| {
+                Error::Http(
+                    StatusCode::NOT_FOUND,
+                    "that invite code does not exist".to_string(),
+                )
             })
         })
     }
@@ -796,345 +866,6 @@ where
         })
     }
 
-    /// Update file counts by extension for the given project and worktree.
-    pub async fn update_worktree_extensions(
-        &self,
-        project_id: ProjectId,
-        worktree_id: u64,
-        extensions: HashMap<String, u32>,
-    ) -> Result<()> {
-        test_support!(self, {
-            if extensions.is_empty() {
-                return Ok(());
-            }
-
-            let mut query = QueryBuilder::new(
-                "INSERT INTO worktree_extensions (project_id, worktree_id, extension, count)",
-            );
-            query.push_values(extensions, |mut query, (extension, count)| {
-                query
-                    .push_bind(project_id)
-                    .push_bind(worktree_id as i32)
-                    .push_bind(extension)
-                    .push_bind(count as i32);
-            });
-            query.push(
-                "
-                ON CONFLICT (project_id, worktree_id, extension) DO UPDATE SET
-                count = excluded.count
-                ",
-            );
-            // query.build().execute(&self.pool).await?;
-
-            Ok(())
-        })
-    }
-
-    /// Get the file counts on the given project keyed by their worktree and extension.
-    pub async fn get_project_extensions(
-        &self,
-        project_id: ProjectId,
-    ) -> Result<HashMap<u64, HashMap<String, usize>>> {
-        test_support!(self, {
-            #[derive(Clone, Debug, Default, FromRow, Serialize, PartialEq)]
-            struct WorktreeExtension {
-                worktree_id: i32,
-                extension: String,
-                count: i32,
-            }
-
-            let query = "
-                SELECT worktree_id, extension, count
-                FROM worktree_extensions
-                WHERE project_id = $1
-            ";
-            let counts = sqlx::query_as::<_, WorktreeExtension>(query)
-                .bind(&project_id)
-                .fetch_all(&self.pool)
-                .await?;
-
-            let mut extension_counts = HashMap::default();
-            for count in counts {
-                extension_counts
-                    .entry(count.worktree_id as u64)
-                    .or_insert_with(HashMap::default)
-                    .insert(count.extension, count.count as usize);
-            }
-            Ok(extension_counts)
-        })
-    }
-
-    /// Record which users have been active in which projects during
-    /// a given period of time.
-    pub async fn record_user_activity(
-        &self,
-        time_period: Range<OffsetDateTime>,
-        projects: &[(UserId, ProjectId)],
-    ) -> Result<()> {
-        test_support!(self, {
-            let query = "
-                INSERT INTO project_activity_periods
-                (ended_at, duration_millis, user_id, project_id)
-                VALUES
-                ($1, $2, $3, $4);
-            ";
-
-            let mut tx = self.pool.begin().await?;
-            let duration_millis =
-                ((time_period.end - time_period.start).as_seconds_f64() * 1000.0) as i32;
-            for (user_id, project_id) in projects {
-                sqlx::query(query)
-                    .bind(time_period.end)
-                    .bind(duration_millis)
-                    .bind(user_id)
-                    .bind(project_id)
-                    .execute(&mut tx)
-                    .await?;
-            }
-            tx.commit().await?;
-
-            Ok(())
-        })
-    }
-
-    /// Get the number of users who have been active in the given
-    /// time period for at least the given time duration.
-    pub async fn get_active_user_count(
-        &self,
-        time_period: Range<OffsetDateTime>,
-        min_duration: Duration,
-        only_collaborative: bool,
-    ) -> Result<usize> {
-        test_support!(self, {
-            let mut with_clause = String::new();
-            with_clause.push_str("WITH\n");
-            with_clause.push_str(
-                "
-                project_durations AS (
-                    SELECT user_id, project_id, SUM(duration_millis) AS project_duration
-                    FROM project_activity_periods
-                    WHERE $1 < ended_at AND ended_at <= $2
-                    GROUP BY user_id, project_id
-                ),
-                ",
-            );
-            with_clause.push_str(
-                "
-                project_collaborators as (
-                    SELECT project_id, COUNT(DISTINCT user_id) as max_collaborators
-                    FROM project_durations
-                    GROUP BY project_id
-                ),
-                ",
-            );
-
-            if only_collaborative {
-                with_clause.push_str(
-                    "
-                    user_durations AS (
-                        SELECT user_id, SUM(project_duration) as total_duration
-                        FROM project_durations, project_collaborators
-                        WHERE
-                            project_durations.project_id = project_collaborators.project_id AND
-                            max_collaborators > 1
-                        GROUP BY user_id
-                        ORDER BY total_duration DESC
-                        LIMIT $3
-                    )
-                    ",
-                );
-            } else {
-                with_clause.push_str(
-                    "
-                    user_durations AS (
-                        SELECT user_id, SUM(project_duration) as total_duration
-                        FROM project_durations
-                        GROUP BY user_id
-                        ORDER BY total_duration DESC
-                        LIMIT $3
-                    )
-                    ",
-                );
-            }
-
-            let query = format!(
-                "
-                {with_clause}
-                SELECT count(user_durations.user_id)
-                FROM user_durations
-                WHERE user_durations.total_duration >= $3
-                "
-            );
-
-            let count: i64 = sqlx::query_scalar(&query)
-                .bind(time_period.start)
-                .bind(time_period.end)
-                .bind(min_duration.as_millis() as i64)
-                .fetch_one(&self.pool)
-                .await?;
-            Ok(count as usize)
-        })
-    }
-
-    /// Get the users that have been most active during the given time period,
-    /// along with the amount of time they have been active in each project.
-    pub async fn get_top_users_activity_summary(
-        &self,
-        time_period: Range<OffsetDateTime>,
-        max_user_count: usize,
-    ) -> Result<Vec<UserActivitySummary>> {
-        test_support!(self, {
-            let query = "
-                WITH
-                    project_durations AS (
-                        SELECT user_id, project_id, SUM(duration_millis) AS project_duration
-                        FROM project_activity_periods
-                        WHERE $1 < ended_at AND ended_at <= $2
-                        GROUP BY user_id, project_id
-                    ),
-                    user_durations AS (
-                        SELECT user_id, SUM(project_duration) as total_duration
-                        FROM project_durations
-                        GROUP BY user_id
-                        ORDER BY total_duration DESC
-                        LIMIT $3
-                    ),
-                    project_collaborators as (
-                        SELECT project_id, COUNT(DISTINCT user_id) as max_collaborators
-                        FROM project_durations
-                        GROUP BY project_id
-                    )
-                SELECT user_durations.user_id, users.github_login, project_durations.project_id, project_duration, max_collaborators
-                FROM user_durations, project_durations, project_collaborators, users
-                WHERE
-                    user_durations.user_id = project_durations.user_id AND
-                    user_durations.user_id = users.id AND
-                    project_durations.project_id = project_collaborators.project_id
-                ORDER BY total_duration DESC, user_id ASC, project_id ASC
-            ";
-
-            let mut rows = sqlx::query_as::<_, (UserId, String, ProjectId, i64, i64)>(query)
-                .bind(time_period.start)
-                .bind(time_period.end)
-                .bind(max_user_count as i32)
-                .fetch(&self.pool);
-
-            let mut result = Vec::<UserActivitySummary>::new();
-            while let Some(row) = rows.next().await {
-                let (user_id, github_login, project_id, duration_millis, project_collaborators) =
-                    row?;
-                let project_id = project_id;
-                let duration = Duration::from_millis(duration_millis as u64);
-                let project_activity = ProjectActivitySummary {
-                    id: project_id,
-                    duration,
-                    max_collaborators: project_collaborators as usize,
-                };
-                if let Some(last_summary) = result.last_mut() {
-                    if last_summary.id == user_id {
-                        last_summary.project_activity.push(project_activity);
-                        continue;
-                    }
-                }
-                result.push(UserActivitySummary {
-                    id: user_id,
-                    project_activity: vec![project_activity],
-                    github_login,
-                });
-            }
-
-            Ok(result)
-        })
-    }
-
-    /// Get the project activity for the given user and time period.
-    pub async fn get_user_activity_timeline(
-        &self,
-        time_period: Range<OffsetDateTime>,
-        user_id: UserId,
-    ) -> Result<Vec<UserActivityPeriod>> {
-        test_support!(self, {
-            const COALESCE_THRESHOLD: Duration = Duration::from_secs(30);
-
-            let query = "
-                SELECT
-                    project_activity_periods.ended_at,
-                    project_activity_periods.duration_millis,
-                    project_activity_periods.project_id,
-                    worktree_extensions.extension,
-                    worktree_extensions.count
-                FROM project_activity_periods
-                LEFT OUTER JOIN
-                    worktree_extensions
-                ON
-                    project_activity_periods.project_id = worktree_extensions.project_id
-                WHERE
-                    project_activity_periods.user_id = $1 AND
-                    $2 < project_activity_periods.ended_at AND
-                    project_activity_periods.ended_at <= $3
-                ORDER BY project_activity_periods.id ASC
-            ";
-
-            let mut rows = sqlx::query_as::<
-                _,
-                (
-                    PrimitiveDateTime,
-                    i32,
-                    ProjectId,
-                    Option<String>,
-                    Option<i32>,
-                ),
-            >(query)
-            .bind(user_id)
-            .bind(time_period.start)
-            .bind(time_period.end)
-            .fetch(&self.pool);
-
-            let mut time_periods: HashMap<ProjectId, Vec<UserActivityPeriod>> = Default::default();
-            while let Some(row) = rows.next().await {
-                let (ended_at, duration_millis, project_id, extension, extension_count) = row?;
-                let ended_at = ended_at.assume_utc();
-                let duration = Duration::from_millis(duration_millis as u64);
-                let started_at = ended_at - duration;
-                let project_time_periods = time_periods.entry(project_id).or_default();
-
-                if let Some(prev_duration) = project_time_periods.last_mut() {
-                    if started_at <= prev_duration.end + COALESCE_THRESHOLD
-                        && ended_at >= prev_duration.start
-                    {
-                        prev_duration.end = cmp::max(prev_duration.end, ended_at);
-                    } else {
-                        project_time_periods.push(UserActivityPeriod {
-                            project_id,
-                            start: started_at,
-                            end: ended_at,
-                            extensions: Default::default(),
-                        });
-                    }
-                } else {
-                    project_time_periods.push(UserActivityPeriod {
-                        project_id,
-                        start: started_at,
-                        end: ended_at,
-                        extensions: Default::default(),
-                    });
-                }
-
-                if let Some((extension, extension_count)) = extension.zip(extension_count) {
-                    project_time_periods
-                        .last_mut()
-                        .unwrap()
-                        .extensions
-                        .insert(extension, extension_count as usize);
-                }
-            }
-
-            let mut durations = time_periods.into_values().flatten().collect::<Vec<_>>();
-            durations.sort_unstable_by_key(|duration| duration.start);
-            Ok(durations)
-        })
-    }
-
     // contacts
 
     pub async fn get_contacts(&self, user_id: UserId) -> Result<Vec<Contact>> {

crates/collab/src/db_tests.rs 🔗

@@ -1,12 +1,10 @@
 use super::db::*;
-use collections::HashMap;
 use gpui::executor::{Background, Deterministic};
-use std::{sync::Arc, time::Duration};
-use time::OffsetDateTime;
+use std::sync::Arc;
 
-#[tokio::test(flavor = "multi_thread")]
+#[gpui::test]
 async fn test_get_users_by_ids() {
-    let test_db = TestDb::new(build_background_executor()).await;
+    let test_db = TestDb::new(build_background_executor());
     let db = test_db.db();
 
     let mut user_ids = Vec::new();
@@ -66,9 +64,9 @@ async fn test_get_users_by_ids() {
     );
 }
 
-#[tokio::test(flavor = "multi_thread")]
+#[gpui::test]
 async fn test_get_user_by_github_account() {
-    let test_db = TestDb::new(build_background_executor()).await;
+    let test_db = TestDb::new(build_background_executor());
     let db = test_db.db();
     let user_id1 = db
         .create_user(
@@ -122,407 +120,9 @@ async fn test_get_user_by_github_account() {
     assert_eq!(user.github_user_id, Some(102));
 }
 
-#[tokio::test(flavor = "multi_thread")]
-async fn test_worktree_extensions() {
-    let test_db = TestDb::new(build_background_executor()).await;
-    let db = test_db.db();
-
-    let user = db
-        .create_user(
-            "u1@example.com",
-            false,
-            NewUserParams {
-                github_login: "u1".into(),
-                github_user_id: 0,
-                invite_count: 0,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-    let project = db.register_project(user).await.unwrap();
-
-    db.update_worktree_extensions(project, 100, Default::default())
-        .await
-        .unwrap();
-    db.update_worktree_extensions(
-        project,
-        100,
-        [("rs".to_string(), 5), ("md".to_string(), 3)]
-            .into_iter()
-            .collect(),
-    )
-    .await
-    .unwrap();
-    db.update_worktree_extensions(
-        project,
-        100,
-        [("rs".to_string(), 6), ("md".to_string(), 5)]
-            .into_iter()
-            .collect(),
-    )
-    .await
-    .unwrap();
-    db.update_worktree_extensions(
-        project,
-        101,
-        [("ts".to_string(), 2), ("md".to_string(), 1)]
-            .into_iter()
-            .collect(),
-    )
-    .await
-    .unwrap();
-
-    assert_eq!(
-        db.get_project_extensions(project).await.unwrap(),
-        [
-            (
-                100,
-                [("rs".into(), 6), ("md".into(), 5),]
-                    .into_iter()
-                    .collect::<HashMap<_, _>>()
-            ),
-            (
-                101,
-                [("ts".into(), 2), ("md".into(), 1),]
-                    .into_iter()
-                    .collect::<HashMap<_, _>>()
-            )
-        ]
-        .into_iter()
-        .collect()
-    );
-}
-
-#[tokio::test(flavor = "multi_thread")]
-async fn test_user_activity() {
-    let test_db = TestDb::new(build_background_executor()).await;
-    let db = test_db.db();
-
-    let mut user_ids = Vec::new();
-    for i in 0..=2 {
-        user_ids.push(
-            db.create_user(
-                &format!("user{i}@example.com"),
-                false,
-                NewUserParams {
-                    github_login: format!("user{i}"),
-                    github_user_id: i,
-                    invite_count: 0,
-                },
-            )
-            .await
-            .unwrap()
-            .user_id,
-        );
-    }
-
-    let project_1 = db.register_project(user_ids[0]).await.unwrap();
-    db.update_worktree_extensions(
-        project_1,
-        1,
-        HashMap::from_iter([("rs".into(), 5), ("md".into(), 7)]),
-    )
-    .await
-    .unwrap();
-    let project_2 = db.register_project(user_ids[1]).await.unwrap();
-    let t0 = OffsetDateTime::now_utc() - Duration::from_secs(60 * 60);
-
-    // User 2 opens a project
-    let t1 = t0 + Duration::from_secs(10);
-    db.record_user_activity(t0..t1, &[(user_ids[1], project_2)])
-        .await
-        .unwrap();
-
-    let t2 = t1 + Duration::from_secs(10);
-    db.record_user_activity(t1..t2, &[(user_ids[1], project_2)])
-        .await
-        .unwrap();
-
-    // User 1 joins the project
-    let t3 = t2 + Duration::from_secs(10);
-    db.record_user_activity(
-        t2..t3,
-        &[(user_ids[1], project_2), (user_ids[0], project_2)],
-    )
-    .await
-    .unwrap();
-
-    // User 1 opens another project
-    let t4 = t3 + Duration::from_secs(10);
-    db.record_user_activity(
-        t3..t4,
-        &[
-            (user_ids[1], project_2),
-            (user_ids[0], project_2),
-            (user_ids[0], project_1),
-        ],
-    )
-    .await
-    .unwrap();
-
-    // User 3 joins that project
-    let t5 = t4 + Duration::from_secs(10);
-    db.record_user_activity(
-        t4..t5,
-        &[
-            (user_ids[1], project_2),
-            (user_ids[0], project_2),
-            (user_ids[0], project_1),
-            (user_ids[2], project_1),
-        ],
-    )
-    .await
-    .unwrap();
-
-    // User 2 leaves
-    let t6 = t5 + Duration::from_secs(5);
-    db.record_user_activity(
-        t5..t6,
-        &[(user_ids[0], project_1), (user_ids[2], project_1)],
-    )
-    .await
-    .unwrap();
-
-    let t7 = t6 + Duration::from_secs(60);
-    let t8 = t7 + Duration::from_secs(10);
-    db.record_user_activity(t7..t8, &[(user_ids[0], project_1)])
-        .await
-        .unwrap();
-
-    assert_eq!(
-        db.get_top_users_activity_summary(t0..t6, 10).await.unwrap(),
-        &[
-            UserActivitySummary {
-                id: user_ids[0],
-                github_login: "user0".to_string(),
-                project_activity: vec![
-                    ProjectActivitySummary {
-                        id: project_1,
-                        duration: Duration::from_secs(25),
-                        max_collaborators: 2
-                    },
-                    ProjectActivitySummary {
-                        id: project_2,
-                        duration: Duration::from_secs(30),
-                        max_collaborators: 2
-                    }
-                ]
-            },
-            UserActivitySummary {
-                id: user_ids[1],
-                github_login: "user1".to_string(),
-                project_activity: vec![ProjectActivitySummary {
-                    id: project_2,
-                    duration: Duration::from_secs(50),
-                    max_collaborators: 2
-                }]
-            },
-            UserActivitySummary {
-                id: user_ids[2],
-                github_login: "user2".to_string(),
-                project_activity: vec![ProjectActivitySummary {
-                    id: project_1,
-                    duration: Duration::from_secs(15),
-                    max_collaborators: 2
-                }]
-            },
-        ]
-    );
-
-    assert_eq!(
-        db.get_active_user_count(t0..t6, Duration::from_secs(56), false)
-            .await
-            .unwrap(),
-        0
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t6, Duration::from_secs(56), true)
-            .await
-            .unwrap(),
-        0
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t6, Duration::from_secs(54), false)
-            .await
-            .unwrap(),
-        1
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t6, Duration::from_secs(54), true)
-            .await
-            .unwrap(),
-        1
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t6, Duration::from_secs(30), false)
-            .await
-            .unwrap(),
-        2
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t6, Duration::from_secs(30), true)
-            .await
-            .unwrap(),
-        2
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t6, Duration::from_secs(10), false)
-            .await
-            .unwrap(),
-        3
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t6, Duration::from_secs(10), true)
-            .await
-            .unwrap(),
-        3
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t1, Duration::from_secs(5), false)
-            .await
-            .unwrap(),
-        1
-    );
-    assert_eq!(
-        db.get_active_user_count(t0..t1, Duration::from_secs(5), true)
-            .await
-            .unwrap(),
-        0
-    );
-
-    assert_eq!(
-        db.get_user_activity_timeline(t3..t6, user_ids[0])
-            .await
-            .unwrap(),
-        &[
-            UserActivityPeriod {
-                project_id: project_1,
-                start: t3,
-                end: t6,
-                extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
-            },
-            UserActivityPeriod {
-                project_id: project_2,
-                start: t3,
-                end: t5,
-                extensions: Default::default(),
-            },
-        ]
-    );
-    assert_eq!(
-        db.get_user_activity_timeline(t0..t8, user_ids[0])
-            .await
-            .unwrap(),
-        &[
-            UserActivityPeriod {
-                project_id: project_2,
-                start: t2,
-                end: t5,
-                extensions: Default::default(),
-            },
-            UserActivityPeriod {
-                project_id: project_1,
-                start: t3,
-                end: t6,
-                extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
-            },
-            UserActivityPeriod {
-                project_id: project_1,
-                start: t7,
-                end: t8,
-                extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
-            },
-        ]
-    );
-}
-
-#[tokio::test(flavor = "multi_thread")]
-async fn test_recent_channel_messages() {
-    let test_db = TestDb::new(build_background_executor()).await;
-    let db = test_db.db();
-    let user = db
-        .create_user(
-            "u@example.com",
-            false,
-            NewUserParams {
-                github_login: "u".into(),
-                github_user_id: 1,
-                invite_count: 0,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-    let org = db.create_org("org", "org").await.unwrap();
-    let channel = db.create_org_channel(org, "channel").await.unwrap();
-    for i in 0..10 {
-        db.create_channel_message(channel, user, &i.to_string(), OffsetDateTime::now_utc(), i)
-            .await
-            .unwrap();
-    }
-
-    let messages = db.get_channel_messages(channel, 5, None).await.unwrap();
-    assert_eq!(
-        messages.iter().map(|m| &m.body).collect::<Vec<_>>(),
-        ["5", "6", "7", "8", "9"]
-    );
-
-    let prev_messages = db
-        .get_channel_messages(channel, 4, Some(messages[0].id))
-        .await
-        .unwrap();
-    assert_eq!(
-        prev_messages.iter().map(|m| &m.body).collect::<Vec<_>>(),
-        ["1", "2", "3", "4"]
-    );
-}
-
-#[tokio::test(flavor = "multi_thread")]
-async fn test_channel_message_nonces() {
-    let test_db = TestDb::new(build_background_executor()).await;
-    let db = test_db.db();
-    let user = db
-        .create_user(
-            "user@example.com",
-            false,
-            NewUserParams {
-                github_login: "user".into(),
-                github_user_id: 1,
-                invite_count: 0,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-    let org = db.create_org("org", "org").await.unwrap();
-    let channel = db.create_org_channel(org, "channel").await.unwrap();
-
-    let msg1_id = db
-        .create_channel_message(channel, user, "1", OffsetDateTime::now_utc(), 1)
-        .await
-        .unwrap();
-    let msg2_id = db
-        .create_channel_message(channel, user, "2", OffsetDateTime::now_utc(), 2)
-        .await
-        .unwrap();
-    let msg3_id = db
-        .create_channel_message(channel, user, "3", OffsetDateTime::now_utc(), 1)
-        .await
-        .unwrap();
-    let msg4_id = db
-        .create_channel_message(channel, user, "4", OffsetDateTime::now_utc(), 2)
-        .await
-        .unwrap();
-
-    assert_ne!(msg1_id, msg2_id);
-    assert_eq!(msg1_id, msg3_id);
-    assert_eq!(msg2_id, msg4_id);
-}
-
-#[tokio::test(flavor = "multi_thread")]
+#[gpui::test]
 async fn test_create_access_tokens() {
-    let test_db = TestDb::new(build_background_executor()).await;
+    let test_db = TestDb::new(build_background_executor());
     let db = test_db.db();
     let user = db
         .create_user(
@@ -571,9 +171,9 @@ fn test_fuzzy_like_string() {
     assert_eq!(DefaultDb::fuzzy_like_string(" z  "), "%z%");
 }
 
-#[tokio::test(flavor = "multi_thread")]
+#[gpui::test]
 async fn test_fuzzy_search_users() {
-    let test_db = TestDb::new(build_background_executor()).await;
+    let test_db = TestDb::new(build_background_executor());
     let db = test_db.db();
     for (i, github_login) in [
         "California",
@@ -619,9 +219,9 @@ async fn test_fuzzy_search_users() {
     }
 }
 
-#[tokio::test(flavor = "multi_thread")]
+#[gpui::test]
 async fn test_add_contacts() {
-    let test_db = TestDb::new(build_background_executor()).await;
+    let test_db = TestDb::new(build_background_executor());
     let db = test_db.db();
 
     let mut user_ids = Vec::new();
@@ -783,9 +383,9 @@ async fn test_add_contacts() {
     );
 }
 
-#[tokio::test(flavor = "multi_thread")]
+#[gpui::test]
 async fn test_invite_codes() {
-    let test_db = TestDb::new(build_background_executor()).await;
+    let test_db = TestDb::new(build_background_executor());
     let db = test_db.db();
     let NewUserResult { user_id: user1, .. } = db
         .create_user(
@@ -978,9 +578,9 @@ async fn test_invite_codes() {
     assert_eq!(invite_count, 1);
 }
 
-#[tokio::test(flavor = "multi_thread")]
+#[gpui::test]
 async fn test_signups() {
-    let test_db = TestDb::new(build_background_executor()).await;
+    let test_db = TestDb::new(build_background_executor());
     let db = test_db.db();
 
     // people sign up on the waitlist
@@ -1124,9 +724,9 @@ async fn test_signups() {
     .unwrap_err();
 }
 
-#[tokio::test(flavor = "multi_thread")]
+#[gpui::test]
 async fn test_metrics_id() {
-    let test_db = TestDb::new(build_background_executor()).await;
+    let test_db = TestDb::new(build_background_executor());
     let db = test_db.db();
 
     let NewUserResult {

crates/collab/src/integration_tests.rs 🔗

@@ -1,14 +1,14 @@
 use crate::{
     db::{NewUserParams, ProjectId, TestDb, UserId},
-    rpc::{Executor, Server, Store},
+    rpc::{Executor, Server},
     AppState,
 };
 use ::rpc::Peer;
 use anyhow::anyhow;
 use call::{room, ActiveCall, ParticipantLocation, Room};
 use client::{
-    self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
-    Credentials, EstablishConnectionError, PeerId, User, UserStore, RECEIVE_TIMEOUT,
+    self, test::FakeHttpClient, Client, Connection, Credentials, EstablishConnectionError, PeerId,
+    User, UserStore, RECEIVE_TIMEOUT,
 };
 use collections::{BTreeMap, HashMap, HashSet};
 use editor::{
@@ -16,10 +16,7 @@ use editor::{
     ToggleCodeActions, Undo,
 };
 use fs::{FakeFs, Fs as _, HomeDir, LineEnding};
-use futures::{
-    channel::{mpsc, oneshot},
-    Future, StreamExt as _,
-};
+use futures::{channel::oneshot, Future, StreamExt as _};
 use gpui::{
     executor::{self, Deterministic},
     geometry::vector::vec2f,
@@ -39,7 +36,6 @@ use project::{
 use rand::prelude::*;
 use serde_json::json;
 use settings::{Formatter, Settings};
-use sqlx::types::time::OffsetDateTime;
 use std::{
     cell::{Cell, RefCell},
     env, mem,
@@ -72,11 +68,8 @@ async fn test_basic_calls(
     cx_b2: &mut TestAppContext,
     cx_c: &mut TestAppContext,
 ) {
-    // let runtime = tokio::runtime::Runtime::new().unwrap();
-    // let _enter_guard = runtime.enter();
-
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
 
     let start = std::time::Instant::now();
 
@@ -279,7 +272,7 @@ async fn test_room_uniqueness(
     cx_c: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let _client_a2 = server.create_client(cx_a2, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -384,7 +377,7 @@ async fn test_leaving_room_on_disconnection(
     cx_b: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -513,7 +506,7 @@ async fn test_calls_on_multiple_connections(
     cx_b2: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b1 = server.create_client(cx_b1, "user_b").await;
     let client_b2 = server.create_client(cx_b2, "user_b").await;
@@ -662,7 +655,7 @@ async fn test_share_project(
 ) {
     deterministic.forbid_parking();
     let (_, window_b) = cx_b.add_window(|_| EmptyView);
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -799,7 +792,7 @@ async fn test_unshare_project(
     cx_c: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -882,7 +875,7 @@ async fn test_host_disconnect(
 ) {
     cx_b.update(editor::init);
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -987,7 +980,7 @@ async fn test_active_call_events(
     cx_b: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     client_a.fs.insert_tree("/a", json!({})).await;
@@ -1076,7 +1069,7 @@ async fn test_room_location(
     cx_b: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     client_a.fs.insert_tree("/a", json!({})).await;
@@ -1242,7 +1235,7 @@ async fn test_propagate_saves_and_fs_changes(
     cx_c: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -1417,7 +1410,7 @@ async fn test_git_diff_base_change(
     cx_b: &mut TestAppContext,
 ) {
     executor.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -1669,7 +1662,7 @@ async fn test_fs_operations(
     cx_b: &mut TestAppContext,
 ) {
     executor.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -1935,7 +1928,7 @@ async fn test_fs_operations(
 #[gpui::test(iterations = 10)]
 async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -1989,7 +1982,7 @@ async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut T
 #[gpui::test(iterations = 10)]
 async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -2048,7 +2041,7 @@ async fn test_editing_while_guest_opens_buffer(
     cx_b: &mut TestAppContext,
 ) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -2095,7 +2088,7 @@ async fn test_leaving_worktree_while_opening_buffer(
     cx_b: &mut TestAppContext,
 ) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -2140,7 +2133,7 @@ async fn test_canceling_buffer_opening(
 ) {
     deterministic.forbid_parking();
 
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -2191,7 +2184,7 @@ async fn test_leaving_project(
     cx_c: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -2324,7 +2317,7 @@ async fn test_collaborating_with_diagnostics(
     cx_c: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -2589,7 +2582,7 @@ async fn test_collaborating_with_diagnostics(
 #[gpui::test(iterations = 10)]
 async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -2763,7 +2756,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
 #[gpui::test(iterations = 10)]
 async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -2856,7 +2849,7 @@ async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut Te
 async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     use project::FormatTrigger;
 
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -2957,7 +2950,7 @@ async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon
 #[gpui::test(iterations = 10)]
 async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -3101,7 +3094,7 @@ async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 #[gpui::test(iterations = 10)]
 async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -3202,7 +3195,7 @@ async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 #[gpui::test(iterations = 10)]
 async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -3281,7 +3274,7 @@ async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContex
 #[gpui::test(iterations = 10)]
 async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -3383,7 +3376,7 @@ async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppC
 #[gpui::test(iterations = 10)]
 async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -3486,7 +3479,7 @@ async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
 #[gpui::test(iterations = 10)]
 async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -3594,7 +3587,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
     mut rng: StdRng,
 ) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -3670,7 +3663,7 @@ async fn test_collaborating_with_code_actions(
 ) {
     cx_a.foreground().forbid_parking();
     cx_b.update(editor::init);
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -3881,7 +3874,7 @@ async fn test_collaborating_with_code_actions(
 async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();
     cx_b.update(editor::init);
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -4073,7 +4066,7 @@ async fn test_language_server_statuses(
     deterministic.forbid_parking();
 
     cx_b.update(editor::init);
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -4177,415 +4170,6 @@ async fn test_language_server_statuses(
     });
 }
 
-#[gpui::test(iterations = 10)]
-async fn test_basic_chat(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
-    cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    // Create an org that includes these 2 users.
-    let db = &server.app_state.db;
-    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
-    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
-        .await
-        .unwrap();
-    db.add_org_member(org_id, client_b.current_user_id(cx_b), false)
-        .await
-        .unwrap();
-
-    // Create a channel that includes all the users.
-    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
-    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
-        .await
-        .unwrap();
-    db.add_channel_member(channel_id, client_b.current_user_id(cx_b), false)
-        .await
-        .unwrap();
-    db.create_channel_message(
-        channel_id,
-        client_b.current_user_id(cx_b),
-        "hello A, it's B.",
-        OffsetDateTime::now_utc(),
-        1,
-    )
-    .await
-    .unwrap();
-
-    let channels_a =
-        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
-    channels_a
-        .condition(cx_a, |list, _| list.available_channels().is_some())
-        .await;
-    channels_a.read_with(cx_a, |list, _| {
-        assert_eq!(
-            list.available_channels().unwrap(),
-            &[ChannelDetails {
-                id: channel_id.to_proto(),
-                name: "test-channel".to_string()
-            }]
-        )
-    });
-    let channel_a = channels_a.update(cx_a, |this, cx| {
-        this.get_channel(channel_id.to_proto(), cx).unwrap()
-    });
-    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
-    channel_a
-        .condition(cx_a, |channel, _| {
-            channel_messages(channel)
-                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
-        })
-        .await;
-
-    let channels_b =
-        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
-    channels_b
-        .condition(cx_b, |list, _| list.available_channels().is_some())
-        .await;
-    channels_b.read_with(cx_b, |list, _| {
-        assert_eq!(
-            list.available_channels().unwrap(),
-            &[ChannelDetails {
-                id: channel_id.to_proto(),
-                name: "test-channel".to_string()
-            }]
-        )
-    });
-
-    let channel_b = channels_b.update(cx_b, |this, cx| {
-        this.get_channel(channel_id.to_proto(), cx).unwrap()
-    });
-    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
-    channel_b
-        .condition(cx_b, |channel, _| {
-            channel_messages(channel)
-                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
-        })
-        .await;
-
-    channel_a
-        .update(cx_a, |channel, cx| {
-            channel
-                .send_message("oh, hi B.".to_string(), cx)
-                .unwrap()
-                .detach();
-            let task = channel.send_message("sup".to_string(), cx).unwrap();
-            assert_eq!(
-                channel_messages(channel),
-                &[
-                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
-                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
-                    ("user_a".to_string(), "sup".to_string(), true)
-                ]
-            );
-            task
-        })
-        .await
-        .unwrap();
-
-    channel_b
-        .condition(cx_b, |channel, _| {
-            channel_messages(channel)
-                == [
-                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
-                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
-                    ("user_a".to_string(), "sup".to_string(), false),
-                ]
-        })
-        .await;
-
-    assert_eq!(
-        server
-            .store()
-            .await
-            .channel(channel_id)
-            .unwrap()
-            .connection_ids
-            .len(),
-        2
-    );
-    cx_b.update(|_| drop(channel_b));
-    server
-        .condition(|state| state.channel(channel_id).unwrap().connection_ids.len() == 1)
-        .await;
-
-    cx_a.update(|_| drop(channel_a));
-    server
-        .condition(|state| state.channel(channel_id).is_none())
-        .await;
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_chat_message_validation(cx_a: &mut TestAppContext) {
-    cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-
-    let db = &server.app_state.db;
-    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
-    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
-    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
-        .await
-        .unwrap();
-    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
-        .await
-        .unwrap();
-
-    let channels_a =
-        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
-    channels_a
-        .condition(cx_a, |list, _| list.available_channels().is_some())
-        .await;
-    let channel_a = channels_a.update(cx_a, |this, cx| {
-        this.get_channel(channel_id.to_proto(), cx).unwrap()
-    });
-
-    // Messages aren't allowed to be too long.
-    channel_a
-        .update(cx_a, |channel, cx| {
-            let long_body = "this is long.\n".repeat(1024);
-            channel.send_message(long_body, cx).unwrap()
-        })
-        .await
-        .unwrap_err();
-
-    // Messages aren't allowed to be blank.
-    channel_a.update(cx_a, |channel, cx| {
-        channel.send_message(String::new(), cx).unwrap_err()
-    });
-
-    // Leading and trailing whitespace are trimmed.
-    channel_a
-        .update(cx_a, |channel, cx| {
-            channel
-                .send_message("\n surrounded by whitespace  \n".to_string(), cx)
-                .unwrap()
-        })
-        .await
-        .unwrap();
-    assert_eq!(
-        db.get_channel_messages(channel_id, 10, None)
-            .await
-            .unwrap()
-            .iter()
-            .map(|m| &m.body)
-            .collect::<Vec<_>>(),
-        &["surrounded by whitespace"]
-    );
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_chat_reconnection(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
-    cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-
-    let mut status_b = client_b.status();
-
-    // Create an org that includes these 2 users.
-    let db = &server.app_state.db;
-    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
-    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
-        .await
-        .unwrap();
-    db.add_org_member(org_id, client_b.current_user_id(cx_b), false)
-        .await
-        .unwrap();
-
-    // Create a channel that includes all the users.
-    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
-    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
-        .await
-        .unwrap();
-    db.add_channel_member(channel_id, client_b.current_user_id(cx_b), false)
-        .await
-        .unwrap();
-    db.create_channel_message(
-        channel_id,
-        client_b.current_user_id(cx_b),
-        "hello A, it's B.",
-        OffsetDateTime::now_utc(),
-        2,
-    )
-    .await
-    .unwrap();
-
-    let channels_a =
-        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
-    channels_a
-        .condition(cx_a, |list, _| list.available_channels().is_some())
-        .await;
-
-    channels_a.read_with(cx_a, |list, _| {
-        assert_eq!(
-            list.available_channels().unwrap(),
-            &[ChannelDetails {
-                id: channel_id.to_proto(),
-                name: "test-channel".to_string()
-            }]
-        )
-    });
-    let channel_a = channels_a.update(cx_a, |this, cx| {
-        this.get_channel(channel_id.to_proto(), cx).unwrap()
-    });
-    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
-    channel_a
-        .condition(cx_a, |channel, _| {
-            channel_messages(channel)
-                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
-        })
-        .await;
-
-    let channels_b =
-        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
-    channels_b
-        .condition(cx_b, |list, _| list.available_channels().is_some())
-        .await;
-    channels_b.read_with(cx_b, |list, _| {
-        assert_eq!(
-            list.available_channels().unwrap(),
-            &[ChannelDetails {
-                id: channel_id.to_proto(),
-                name: "test-channel".to_string()
-            }]
-        )
-    });
-
-    let channel_b = channels_b.update(cx_b, |this, cx| {
-        this.get_channel(channel_id.to_proto(), cx).unwrap()
-    });
-    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
-    channel_b
-        .condition(cx_b, |channel, _| {
-            channel_messages(channel)
-                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
-        })
-        .await;
-
-    // Disconnect client B, ensuring we can still access its cached channel data.
-    server.forbid_connections();
-    server.disconnect_client(client_b.peer_id().unwrap());
-    cx_b.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
-    while !matches!(
-        status_b.next().await,
-        Some(client::Status::ReconnectionError { .. })
-    ) {}
-
-    channels_b.read_with(cx_b, |channels, _| {
-        assert_eq!(
-            channels.available_channels().unwrap(),
-            [ChannelDetails {
-                id: channel_id.to_proto(),
-                name: "test-channel".to_string()
-            }]
-        )
-    });
-    channel_b.read_with(cx_b, |channel, _| {
-        assert_eq!(
-            channel_messages(channel),
-            [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
-        )
-    });
-
-    // Send a message from client B while it is disconnected.
-    channel_b
-        .update(cx_b, |channel, cx| {
-            let task = channel
-                .send_message("can you see this?".to_string(), cx)
-                .unwrap();
-            assert_eq!(
-                channel_messages(channel),
-                &[
-                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
-                    ("user_b".to_string(), "can you see this?".to_string(), true)
-                ]
-            );
-            task
-        })
-        .await
-        .unwrap_err();
-
-    // Send a message from client A while B is disconnected.
-    channel_a
-        .update(cx_a, |channel, cx| {
-            channel
-                .send_message("oh, hi B.".to_string(), cx)
-                .unwrap()
-                .detach();
-            let task = channel.send_message("sup".to_string(), cx).unwrap();
-            assert_eq!(
-                channel_messages(channel),
-                &[
-                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
-                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
-                    ("user_a".to_string(), "sup".to_string(), true)
-                ]
-            );
-            task
-        })
-        .await
-        .unwrap();
-
-    // Give client B a chance to reconnect.
-    server.allow_connections();
-    cx_b.foreground().advance_clock(Duration::from_secs(10));
-
-    // Verify that B sees the new messages upon reconnection, as well as the message client B
-    // sent while offline.
-    channel_b
-        .condition(cx_b, |channel, _| {
-            channel_messages(channel)
-                == [
-                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
-                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
-                    ("user_a".to_string(), "sup".to_string(), false),
-                    ("user_b".to_string(), "can you see this?".to_string(), false),
-                ]
-        })
-        .await;
-
-    // Ensure client A and B can communicate normally after reconnection.
-    channel_a
-        .update(cx_a, |channel, cx| {
-            channel.send_message("you online?".to_string(), cx).unwrap()
-        })
-        .await
-        .unwrap();
-    channel_b
-        .condition(cx_b, |channel, _| {
-            channel_messages(channel)
-                == [
-                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
-                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
-                    ("user_a".to_string(), "sup".to_string(), false),
-                    ("user_b".to_string(), "can you see this?".to_string(), false),
-                    ("user_a".to_string(), "you online?".to_string(), false),
-                ]
-        })
-        .await;
-
-    channel_b
-        .update(cx_b, |channel, cx| {
-            channel.send_message("yep".to_string(), cx).unwrap()
-        })
-        .await
-        .unwrap();
-    channel_a
-        .condition(cx_a, |channel, _| {
-            channel_messages(channel)
-                == [
-                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
-                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
-                    ("user_a".to_string(), "sup".to_string(), false),
-                    ("user_b".to_string(), "can you see this?".to_string(), false),
-                    ("user_a".to_string(), "you online?".to_string(), false),
-                    ("user_b".to_string(), "yep".to_string(), false),
-                ]
-        })
-        .await;
-}
-
 #[gpui::test(iterations = 10)]
 async fn test_contacts(
     deterministic: Arc<Deterministic>,
@@ -4594,7 +4178,7 @@ async fn test_contacts(
     cx_c: &mut TestAppContext,
 ) {
     cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     let client_c = server.create_client(cx_c, "user_c").await;
@@ -4920,7 +4504,7 @@ async fn test_contact_requests(
     cx_a.foreground().forbid_parking();
 
     // Connect to a server as 3 clients.
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_a2 = server.create_client(cx_a2, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -5101,7 +4685,7 @@ async fn test_following(
     cx_a.update(editor::init);
     cx_b.update(editor::init);
 
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -5375,7 +4959,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
     cx_a.update(editor::init);
     cx_b.update(editor::init);
 
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -5553,7 +5137,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
     cx_b.update(editor::init);
 
     // 2 clients connect to a server.
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -5727,7 +5311,7 @@ async fn test_peers_simultaneously_following_each_other(
     cx_a.update(editor::init);
     cx_b.update(editor::init);
 
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let mut server = TestServer::start(cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
     server
@@ -5797,7 +5381,7 @@ async fn test_random_collaboration(
         .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
         .unwrap_or(10);
 
-    let mut server = TestServer::start(cx.foreground(), cx.background()).await;
+    let mut server = TestServer::start(cx.background()).await;
     let db = server.app_state.db.clone();
 
     let mut available_guests = Vec::new();
@@ -6084,8 +5668,6 @@ struct TestServer {
     peer: Arc<Peer>,
     app_state: Arc<AppState>,
     server: Arc<Server>,
-    foreground: Rc<executor::Foreground>,
-    notifications: mpsc::UnboundedReceiver<()>,
     connection_killers: Arc<Mutex<HashMap<PeerId, Arc<AtomicBool>>>>,
     forbid_connections: Arc<AtomicBool>,
     _test_db: TestDb,
@@ -6093,18 +5675,10 @@ struct TestServer {
 }
 
 impl TestServer {
-    async fn start(
-        foreground: Rc<executor::Foreground>,
-        background: Arc<executor::Background>,
-    ) -> Self {
+    async fn start(background: Arc<executor::Background>) -> Self {
         static NEXT_LIVE_KIT_SERVER_ID: AtomicUsize = AtomicUsize::new(0);
 
-        let test_db = tokio::runtime::Builder::new_current_thread()
-            .enable_io()
-            .enable_time()
-            .build()
-            .unwrap()
-            .block_on(TestDb::new(background.clone()));
+        let test_db = TestDb::new(background.clone());
         let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst);
         let live_kit_server = live_kit_client::TestServer::create(
             format!("http://livekit.{}.test", live_kit_server_id),
@@ -6115,14 +5689,11 @@ impl TestServer {
         .unwrap();
         let app_state = Self::build_app_state(&test_db, &live_kit_server).await;
         let peer = Peer::new();
-        let notifications = mpsc::unbounded();
-        let server = Server::new(app_state.clone(), Some(notifications.0));
+        let server = Server::new(app_state.clone());
         Self {
             peer,
             app_state,
             server,
-            foreground,
-            notifications: notifications.1,
             connection_killers: Default::default(),
             forbid_connections: Default::default(),
             _test_db: test_db,
@@ -6238,7 +5809,6 @@ impl TestServer {
             default_item_factory: |_, _| unimplemented!(),
         });
 
-        Channel::init(&client);
         Project::init(&client);
         cx.update(|cx| {
             workspace::init(app_state.clone(), cx);
@@ -6339,21 +5909,6 @@ impl TestServer {
             config: Default::default(),
         })
     }
-
-    async fn condition<F>(&mut self, mut predicate: F)
-    where
-        F: FnMut(&Store) -> bool,
-    {
-        assert!(
-            self.foreground.parking_forbidden(),
-            "you must call forbid_parking to use server conditions so we don't block indefinitely"
-        );
-        while !(predicate)(&*self.server.store.lock().await) {
-            self.foreground.start_waiting();
-            self.notifications.next().await;
-            self.foreground.finish_waiting();
-        }
-    }
 }
 
 impl Deref for TestServer {
@@ -7069,20 +6624,6 @@ impl Executor for Arc<gpui::executor::Background> {
     }
 }
 
-fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> {
-    channel
-        .messages()
-        .cursor::<()>()
-        .map(|m| {
-            (
-                m.sender.github_login.clone(),
-                m.body.clone(),
-                m.is_pending(),
-            )
-        })
-        .collect()
-}
-
 #[derive(Debug, Eq, PartialEq)]
 struct RoomParticipants {
     remote: Vec<String>,

crates/collab/src/main.rs 🔗

@@ -121,9 +121,7 @@ async fn main() -> Result<()> {
             let listener = TcpListener::bind(&format!("0.0.0.0:{}", state.config.http_port))
                 .expect("failed to bind TCP listener");
 
-            let rpc_server = rpc::Server::new(state.clone(), None);
-            rpc_server
-                .start_recording_project_activity(Duration::from_secs(5 * 60), rpc::RealExecutor);
+            let rpc_server = rpc::Server::new(state.clone());
 
             let app = api::routes(rpc_server.clone(), state.clone())
                 .merge(rpc::routes(rpc_server.clone()))

crates/collab/src/rpc.rs 🔗

@@ -2,7 +2,7 @@ mod store;
 
 use crate::{
     auth,
-    db::{self, ChannelId, MessageId, ProjectId, User, UserId},
+    db::{self, ProjectId, User, UserId},
     AppState, Result,
 };
 use anyhow::anyhow;
@@ -24,7 +24,7 @@ use axum::{
 };
 use collections::{HashMap, HashSet};
 use futures::{
-    channel::{mpsc, oneshot},
+    channel::oneshot,
     future::{self, BoxFuture},
     stream::FuturesUnordered,
     FutureExt, SinkExt, StreamExt, TryStreamExt,
@@ -51,7 +51,6 @@ use std::{
     time::Duration,
 };
 pub use store::{Store, Worktree};
-use time::OffsetDateTime;
 use tokio::{
     sync::{Mutex, MutexGuard},
     time::Sleep,
@@ -62,10 +61,6 @@ use tracing::{info_span, instrument, Instrument};
 lazy_static! {
     static ref METRIC_CONNECTIONS: IntGauge =
         register_int_gauge!("connections", "number of connections").unwrap();
-    static ref METRIC_REGISTERED_PROJECTS: IntGauge =
-        register_int_gauge!("registered_projects", "number of registered projects").unwrap();
-    static ref METRIC_ACTIVE_PROJECTS: IntGauge =
-        register_int_gauge!("active_projects", "number of active projects").unwrap();
     static ref METRIC_SHARED_PROJECTS: IntGauge = register_int_gauge!(
         "shared_projects",
         "number of open projects with one or more guests"
@@ -95,7 +90,6 @@ pub struct Server {
     pub(crate) store: Mutex<Store>,
     app_state: Arc<AppState>,
     handlers: HashMap<TypeId, MessageHandler>,
-    notifications: Option<mpsc::UnboundedSender<()>>,
 }
 
 pub trait Executor: Send + Clone {
@@ -107,9 +101,6 @@ pub trait Executor: Send + Clone {
 #[derive(Clone)]
 pub struct RealExecutor;
 
-const MESSAGE_COUNT_PER_PAGE: usize = 100;
-const MAX_MESSAGE_LEN: usize = 1024;
-
 pub(crate) struct StoreGuard<'a> {
     guard: MutexGuard<'a, Store>,
     _not_send: PhantomData<Rc<()>>,
@@ -132,16 +123,12 @@ where
 }
 
 impl Server {
-    pub fn new(
-        app_state: Arc<AppState>,
-        notifications: Option<mpsc::UnboundedSender<()>>,
-    ) -> Arc<Self> {
+    pub fn new(app_state: Arc<AppState>) -> Arc<Self> {
         let mut server = Self {
             peer: Peer::new(),
             app_state,
             store: Default::default(),
             handlers: Default::default(),
-            notifications,
         };
 
         server
@@ -158,9 +145,7 @@ impl Server {
             .add_request_handler(Server::join_project)
             .add_message_handler(Server::leave_project)
             .add_message_handler(Server::update_project)
-            .add_message_handler(Server::register_project_activity)
             .add_request_handler(Server::update_worktree)
-            .add_message_handler(Server::update_worktree_extensions)
             .add_message_handler(Server::start_language_server)
             .add_message_handler(Server::update_language_server)
             .add_message_handler(Server::update_diagnostic_summary)
@@ -194,19 +179,14 @@ impl Server {
             .add_message_handler(Server::buffer_reloaded)
             .add_message_handler(Server::buffer_saved)
             .add_request_handler(Server::save_buffer)
-            .add_request_handler(Server::get_channels)
             .add_request_handler(Server::get_users)
             .add_request_handler(Server::fuzzy_search_users)
             .add_request_handler(Server::request_contact)
             .add_request_handler(Server::remove_contact)
             .add_request_handler(Server::respond_to_contact_request)
-            .add_request_handler(Server::join_channel)
-            .add_message_handler(Server::leave_channel)
-            .add_request_handler(Server::send_channel_message)
             .add_request_handler(Server::follow)
             .add_message_handler(Server::unfollow)
             .add_message_handler(Server::update_followers)
-            .add_request_handler(Server::get_channel_messages)
             .add_message_handler(Server::update_diff_base)
             .add_request_handler(Server::get_private_user_info);
 
@@ -290,58 +270,6 @@ impl Server {
         })
     }
 
-    /// Start a long lived task that records which users are active in which projects.
-    pub fn start_recording_project_activity<E: 'static + Executor>(
-        self: &Arc<Self>,
-        interval: Duration,
-        executor: E,
-    ) {
-        executor.spawn_detached({
-            let this = Arc::downgrade(self);
-            let executor = executor.clone();
-            async move {
-                let mut period_start = OffsetDateTime::now_utc();
-                let mut active_projects = Vec::<(UserId, ProjectId)>::new();
-                loop {
-                    let sleep = executor.sleep(interval);
-                    sleep.await;
-                    let this = if let Some(this) = this.upgrade() {
-                        this
-                    } else {
-                        break;
-                    };
-
-                    active_projects.clear();
-                    active_projects.extend(this.store().await.projects().flat_map(
-                        |(project_id, project)| {
-                            project.guests.values().chain([&project.host]).filter_map(
-                                |collaborator| {
-                                    if !collaborator.admin
-                                        && collaborator
-                                            .last_activity
-                                            .map_or(false, |activity| activity > period_start)
-                                    {
-                                        Some((collaborator.user_id, *project_id))
-                                    } else {
-                                        None
-                                    }
-                                },
-                            )
-                        },
-                    ));
-
-                    let period_end = OffsetDateTime::now_utc();
-                    this.app_state
-                        .db
-                        .record_user_activity(period_start..period_end, &active_projects)
-                        .await
-                        .trace_err();
-                    period_start = period_end;
-                }
-            }
-        });
-    }
-
     pub fn handle_connection<E: Executor>(
         self: &Arc<Self>,
         connection: Connection,
@@ -432,18 +360,11 @@ impl Server {
                             let span = tracing::info_span!("receive message", %user_id, %login, %connection_id, %address, type_name);
                             let span_enter = span.enter();
                             if let Some(handler) = this.handlers.get(&message.payload_type_id()) {
-                                let notifications = this.notifications.clone();
                                 let is_background = message.is_background();
                                 let handle_message = (handler)(this.clone(), message);
-
                                 drop(span_enter);
-                                let handle_message = async move {
-                                    handle_message.await;
-                                    if let Some(mut notifications) = notifications {
-                                        let _ = notifications.send(()).await;
-                                    }
-                                }.instrument(span);
 
+                                let handle_message = handle_message.instrument(span);
                                 if is_background {
                                     executor.spawn_detached(handle_message);
                                 } else {
@@ -1172,17 +1093,6 @@ impl Server {
         Ok(())
     }
 
-    async fn register_project_activity(
-        self: Arc<Server>,
-        request: TypedEnvelope<proto::RegisterProjectActivity>,
-    ) -> Result<()> {
-        self.store().await.register_project_activity(
-            ProjectId::from_proto(request.payload.project_id),
-            request.sender_id,
-        )?;
-        Ok(())
-    }
-
     async fn update_worktree(
         self: Arc<Server>,
         request: TypedEnvelope<proto::UpdateWorktree>,
@@ -1209,25 +1119,6 @@ impl Server {
         Ok(())
     }
 
-    async fn update_worktree_extensions(
-        self: Arc<Server>,
-        request: TypedEnvelope<proto::UpdateWorktreeExtensions>,
-    ) -> Result<()> {
-        let project_id = ProjectId::from_proto(request.payload.project_id);
-        let worktree_id = request.payload.worktree_id;
-        let extensions = request
-            .payload
-            .extensions
-            .into_iter()
-            .zip(request.payload.counts)
-            .collect();
-        self.app_state
-            .db
-            .update_worktree_extensions(project_id, worktree_id, extensions)
-            .await?;
-        Ok(())
-    }
-
     async fn update_diagnostic_summary(
         self: Arc<Server>,
         request: TypedEnvelope<proto::UpdateDiagnosticSummary>,
@@ -1363,8 +1254,7 @@ impl Server {
     ) -> Result<()> {
         let project_id = ProjectId::from_proto(request.payload.project_id);
         let receiver_ids = {
-            let mut store = self.store().await;
-            store.register_project_activity(project_id, request.sender_id)?;
+            let store = self.store().await;
             store.project_connection_ids(project_id, request.sender_id)?
         };
 
@@ -1430,15 +1320,13 @@ impl Server {
         let leader_id = ConnectionId(request.payload.leader_id);
         let follower_id = request.sender_id;
         {
-            let mut store = self.store().await;
+            let store = self.store().await;
             if !store
                 .project_connection_ids(project_id, follower_id)?
                 .contains(&leader_id)
             {
                 Err(anyhow!("no such peer"))?;
             }
-
-            store.register_project_activity(project_id, follower_id)?;
         }
 
         let mut response_payload = self
@@ -1455,14 +1343,13 @@ impl Server {
     async fn unfollow(self: Arc<Self>, request: TypedEnvelope<proto::Unfollow>) -> Result<()> {
         let project_id = ProjectId::from_proto(request.payload.project_id);
         let leader_id = ConnectionId(request.payload.leader_id);
-        let mut store = self.store().await;
+        let store = self.store().await;
         if !store
             .project_connection_ids(project_id, request.sender_id)?
             .contains(&leader_id)
         {
             Err(anyhow!("no such peer"))?;
         }
-        store.register_project_activity(project_id, request.sender_id)?;
         self.peer
             .forward_send(request.sender_id, leader_id, request.payload)?;
         Ok(())
@@ -1473,8 +1360,7 @@ impl Server {
         request: TypedEnvelope<proto::UpdateFollowers>,
     ) -> Result<()> {
         let project_id = ProjectId::from_proto(request.payload.project_id);
-        let mut store = self.store().await;
-        store.register_project_activity(project_id, request.sender_id)?;
+        let store = self.store().await;
         let connection_ids = store.project_connection_ids(project_id, request.sender_id)?;
         let leader_id = request
             .payload
@@ -1495,28 +1381,6 @@ impl Server {
         Ok(())
     }
 
-    async fn get_channels(
-        self: Arc<Server>,
-        request: TypedEnvelope<proto::GetChannels>,
-        response: Response<proto::GetChannels>,
-    ) -> Result<()> {
-        let user_id = self
-            .store()
-            .await
-            .user_id_for_connection(request.sender_id)?;
-        let channels = self.app_state.db.get_accessible_channels(user_id).await?;
-        response.send(proto::GetChannelsResponse {
-            channels: channels
-                .into_iter()
-                .map(|chan| proto::Channel {
-                    id: chan.id.to_proto(),
-                    name: chan.name,
-                })
-                .collect(),
-        })?;
-        Ok(())
-    }
-
     async fn get_users(
         self: Arc<Server>,
         request: TypedEnvelope<proto::GetUsers>,
@@ -1712,175 +1576,6 @@ impl Server {
         Ok(())
     }
 
-    async fn join_channel(
-        self: Arc<Self>,
-        request: TypedEnvelope<proto::JoinChannel>,
-        response: Response<proto::JoinChannel>,
-    ) -> Result<()> {
-        let user_id = self
-            .store()
-            .await
-            .user_id_for_connection(request.sender_id)?;
-        let channel_id = ChannelId::from_proto(request.payload.channel_id);
-        if !self
-            .app_state
-            .db
-            .can_user_access_channel(user_id, channel_id)
-            .await?
-        {
-            Err(anyhow!("access denied"))?;
-        }
-
-        self.store()
-            .await
-            .join_channel(request.sender_id, channel_id);
-        let messages = self
-            .app_state
-            .db
-            .get_channel_messages(channel_id, MESSAGE_COUNT_PER_PAGE, None)
-            .await?
-            .into_iter()
-            .map(|msg| proto::ChannelMessage {
-                id: msg.id.to_proto(),
-                body: msg.body,
-                timestamp: msg.sent_at.unix_timestamp() as u64,
-                sender_id: msg.sender_id.to_proto(),
-                nonce: Some(msg.nonce.as_u128().into()),
-            })
-            .collect::<Vec<_>>();
-        response.send(proto::JoinChannelResponse {
-            done: messages.len() < MESSAGE_COUNT_PER_PAGE,
-            messages,
-        })?;
-        Ok(())
-    }
-
-    async fn leave_channel(
-        self: Arc<Self>,
-        request: TypedEnvelope<proto::LeaveChannel>,
-    ) -> Result<()> {
-        let user_id = self
-            .store()
-            .await
-            .user_id_for_connection(request.sender_id)?;
-        let channel_id = ChannelId::from_proto(request.payload.channel_id);
-        if !self
-            .app_state
-            .db
-            .can_user_access_channel(user_id, channel_id)
-            .await?
-        {
-            Err(anyhow!("access denied"))?;
-        }
-
-        self.store()
-            .await
-            .leave_channel(request.sender_id, channel_id);
-
-        Ok(())
-    }
-
-    async fn send_channel_message(
-        self: Arc<Self>,
-        request: TypedEnvelope<proto::SendChannelMessage>,
-        response: Response<proto::SendChannelMessage>,
-    ) -> Result<()> {
-        let channel_id = ChannelId::from_proto(request.payload.channel_id);
-        let user_id;
-        let connection_ids;
-        {
-            let state = self.store().await;
-            user_id = state.user_id_for_connection(request.sender_id)?;
-            connection_ids = state.channel_connection_ids(channel_id)?;
-        }
-
-        // Validate the message body.
-        let body = request.payload.body.trim().to_string();
-        if body.len() > MAX_MESSAGE_LEN {
-            return Err(anyhow!("message is too long"))?;
-        }
-        if body.is_empty() {
-            return Err(anyhow!("message can't be blank"))?;
-        }
-
-        let timestamp = OffsetDateTime::now_utc();
-        let nonce = request
-            .payload
-            .nonce
-            .ok_or_else(|| anyhow!("nonce can't be blank"))?;
-
-        let message_id = self
-            .app_state
-            .db
-            .create_channel_message(channel_id, user_id, &body, timestamp, nonce.clone().into())
-            .await?
-            .to_proto();
-        let message = proto::ChannelMessage {
-            sender_id: user_id.to_proto(),
-            id: message_id,
-            body,
-            timestamp: timestamp.unix_timestamp() as u64,
-            nonce: Some(nonce),
-        };
-        broadcast(request.sender_id, connection_ids, |conn_id| {
-            self.peer.send(
-                conn_id,
-                proto::ChannelMessageSent {
-                    channel_id: channel_id.to_proto(),
-                    message: Some(message.clone()),
-                },
-            )
-        });
-        response.send(proto::SendChannelMessageResponse {
-            message: Some(message),
-        })?;
-        Ok(())
-    }
-
-    async fn get_channel_messages(
-        self: Arc<Self>,
-        request: TypedEnvelope<proto::GetChannelMessages>,
-        response: Response<proto::GetChannelMessages>,
-    ) -> Result<()> {
-        let user_id = self
-            .store()
-            .await
-            .user_id_for_connection(request.sender_id)?;
-        let channel_id = ChannelId::from_proto(request.payload.channel_id);
-        if !self
-            .app_state
-            .db
-            .can_user_access_channel(user_id, channel_id)
-            .await?
-        {
-            Err(anyhow!("access denied"))?;
-        }
-
-        let messages = self
-            .app_state
-            .db
-            .get_channel_messages(
-                channel_id,
-                MESSAGE_COUNT_PER_PAGE,
-                Some(MessageId::from_proto(request.payload.before_message_id)),
-            )
-            .await?
-            .into_iter()
-            .map(|msg| proto::ChannelMessage {
-                id: msg.id.to_proto(),
-                body: msg.body,
-                timestamp: msg.sent_at.unix_timestamp() as u64,
-                sender_id: msg.sender_id.to_proto(),
-                nonce: Some(msg.nonce.as_u128().into()),
-            })
-            .collect::<Vec<_>>();
-        response.send(proto::GetChannelMessagesResponse {
-            done: messages.len() < MESSAGE_COUNT_PER_PAGE,
-            messages,
-        })?;
-        Ok(())
-    }
-
     async fn update_diff_base(
         self: Arc<Server>,
         request: TypedEnvelope<proto::UpdateDiffBase>,
@@ -2061,11 +1756,8 @@ pub async fn handle_websocket_request(
 }
 
 pub async fn handle_metrics(Extension(server): Extension<Arc<Server>>) -> axum::response::Response {
-    // We call `store_mut` here for its side effects of updating metrics.
     let metrics = server.store().await.metrics();
     METRIC_CONNECTIONS.set(metrics.connections as _);
-    METRIC_REGISTERED_PROJECTS.set(metrics.registered_projects as _);
-    METRIC_ACTIVE_PROJECTS.set(metrics.active_projects as _);
     METRIC_SHARED_PROJECTS.set(metrics.shared_projects as _);
 
     let encoder = prometheus::TextEncoder::new();

crates/collab/src/rpc/store.rs 🔗

@@ -1,11 +1,10 @@
-use crate::db::{self, ChannelId, ProjectId, UserId};
+use crate::db::{self, ProjectId, UserId};
 use anyhow::{anyhow, Result};
 use collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet};
 use nanoid::nanoid;
 use rpc::{proto, ConnectionId};
 use serde::Serialize;
-use std::{borrow::Cow, mem, path::PathBuf, str, time::Duration};
-use time::OffsetDateTime;
+use std::{borrow::Cow, mem, path::PathBuf, str};
 use tracing::instrument;
 use util::post_inc;
 
@@ -18,8 +17,6 @@ pub struct Store {
     next_room_id: RoomId,
     rooms: BTreeMap<RoomId, proto::Room>,
     projects: BTreeMap<ProjectId, Project>,
-    #[serde(skip)]
-    channels: BTreeMap<ChannelId, Channel>,
 }
 
 #[derive(Default, Serialize)]
@@ -33,7 +30,6 @@ struct ConnectionState {
     user_id: UserId,
     admin: bool,
     projects: BTreeSet<ProjectId>,
-    channels: HashSet<ChannelId>,
 }
 
 #[derive(Copy, Clone, Eq, PartialEq, Serialize)]
@@ -60,8 +56,6 @@ pub struct Project {
 pub struct Collaborator {
     pub replica_id: ReplicaId,
     pub user_id: UserId,
-    #[serde(skip)]
-    pub last_activity: Option<OffsetDateTime>,
     pub admin: bool,
 }
 
@@ -78,11 +72,6 @@ pub struct Worktree {
     pub is_complete: bool,
 }
 
-#[derive(Default)]
-pub struct Channel {
-    pub connection_ids: HashSet<ConnectionId>,
-}
-
 pub type ReplicaId = u16;
 
 #[derive(Default)]
@@ -113,38 +102,23 @@ pub struct LeftRoom<'a> {
 #[derive(Copy, Clone)]
 pub struct Metrics {
     pub connections: usize,
-    pub registered_projects: usize,
-    pub active_projects: usize,
     pub shared_projects: usize,
 }
 
 impl Store {
     pub fn metrics(&self) -> Metrics {
-        const ACTIVE_PROJECT_TIMEOUT: Duration = Duration::from_secs(60);
-        let active_window_start = OffsetDateTime::now_utc() - ACTIVE_PROJECT_TIMEOUT;
-
         let connections = self.connections.values().filter(|c| !c.admin).count();
-        let mut registered_projects = 0;
-        let mut active_projects = 0;
         let mut shared_projects = 0;
         for project in self.projects.values() {
             if let Some(connection) = self.connections.get(&project.host_connection_id) {
                 if !connection.admin {
-                    registered_projects += 1;
-                    if project.is_active_since(active_window_start) {
-                        active_projects += 1;
-                        if !project.guests.is_empty() {
-                            shared_projects += 1;
-                        }
-                    }
+                    shared_projects += 1;
                 }
             }
         }
 
         Metrics {
             connections,
-            registered_projects,
-            active_projects,
             shared_projects,
         }
     }
@@ -162,7 +136,6 @@ impl Store {
                 user_id,
                 admin,
                 projects: Default::default(),
-                channels: Default::default(),
             },
         );
         let connected_user = self.connected_users.entry(user_id).or_default();
@@ -201,18 +174,12 @@ impl Store {
             .ok_or_else(|| anyhow!("no such connection"))?;
 
         let user_id = connection.user_id;
-        let connection_channels = mem::take(&mut connection.channels);
 
         let mut result = RemovedConnectionState {
             user_id,
             ..Default::default()
         };
 
-        // Leave all channels.
-        for channel_id in connection_channels {
-            self.leave_channel(connection_id, channel_id);
-        }
-
         let connected_user = self.connected_users.get(&user_id).unwrap();
         if let Some(active_call) = connected_user.active_call.as_ref() {
             let room_id = active_call.room_id;
@@ -238,34 +205,6 @@ impl Store {
         Ok(result)
     }
 
-    #[cfg(test)]
-    pub fn channel(&self, id: ChannelId) -> Option<&Channel> {
-        self.channels.get(&id)
-    }
-
-    pub fn join_channel(&mut self, connection_id: ConnectionId, channel_id: ChannelId) {
-        if let Some(connection) = self.connections.get_mut(&connection_id) {
-            connection.channels.insert(channel_id);
-            self.channels
-                .entry(channel_id)
-                .or_default()
-                .connection_ids
-                .insert(connection_id);
-        }
-    }
-
-    pub fn leave_channel(&mut self, connection_id: ConnectionId, channel_id: ChannelId) {
-        if let Some(connection) = self.connections.get_mut(&connection_id) {
-            connection.channels.remove(&channel_id);
-            if let btree_map::Entry::Occupied(mut entry) = self.channels.entry(channel_id) {
-                entry.get_mut().connection_ids.remove(&connection_id);
-                if entry.get_mut().connection_ids.is_empty() {
-                    entry.remove();
-                }
-            }
-        }
-    }
-
     pub fn user_id_for_connection(&self, connection_id: ConnectionId) -> Result<UserId> {
         Ok(self
             .connections
@@ -760,7 +699,6 @@ impl Store {
                 host: Collaborator {
                     user_id: connection.user_id,
                     replica_id: 0,
-                    last_activity: None,
                     admin: connection.admin,
                 },
                 guests: Default::default(),
@@ -959,12 +897,10 @@ impl Store {
             Collaborator {
                 replica_id,
                 user_id: connection.user_id,
-                last_activity: Some(OffsetDateTime::now_utc()),
                 admin: connection.admin,
             },
         );
 
-        project.host.last_activity = Some(OffsetDateTime::now_utc());
         Ok((project, replica_id))
     }
 
@@ -1056,44 +992,12 @@ impl Store {
             .connection_ids())
     }
 
-    pub fn channel_connection_ids(&self, channel_id: ChannelId) -> Result<Vec<ConnectionId>> {
-        Ok(self
-            .channels
-            .get(&channel_id)
-            .ok_or_else(|| anyhow!("no such channel"))?
-            .connection_ids())
-    }
-
     pub fn project(&self, project_id: ProjectId) -> Result<&Project> {
         self.projects
             .get(&project_id)
             .ok_or_else(|| anyhow!("no such project"))
     }
 
-    pub fn register_project_activity(
-        &mut self,
-        project_id: ProjectId,
-        connection_id: ConnectionId,
-    ) -> Result<()> {
-        let project = self
-            .projects
-            .get_mut(&project_id)
-            .ok_or_else(|| anyhow!("no such project"))?;
-        let collaborator = if connection_id == project.host_connection_id {
-            &mut project.host
-        } else if let Some(guest) = project.guests.get_mut(&connection_id) {
-            guest
-        } else {
-            return Err(anyhow!("no such project"))?;
-        };
-        collaborator.last_activity = Some(OffsetDateTime::now_utc());
-        Ok(())
-    }
-
-    pub fn projects(&self) -> impl Iterator<Item = (&ProjectId, &Project)> {
-        self.projects.iter()
-    }
-
     pub fn read_project(
         &self,
         project_id: ProjectId,
@@ -1154,10 +1058,7 @@ impl Store {
                     }
                 }
             }
-            for channel_id in &connection.channels {
-                let channel = self.channels.get(channel_id).unwrap();
-                assert!(channel.connection_ids.contains(connection_id));
-            }
+
             assert!(self
                 .connected_users
                 .get(&connection.user_id)
@@ -1253,28 +1154,10 @@ impl Store {
                 "project was not shared in room"
             );
         }
-
-        for (channel_id, channel) in &self.channels {
-            for connection_id in &channel.connection_ids {
-                let connection = self.connections.get(connection_id).unwrap();
-                assert!(connection.channels.contains(channel_id));
-            }
-        }
     }
 }
 
 impl Project {
-    fn is_active_since(&self, start_time: OffsetDateTime) -> bool {
-        self.guests
-            .values()
-            .chain([&self.host])
-            .any(|collaborator| {
-                collaborator
-                    .last_activity
-                    .map_or(false, |active_time| active_time > start_time)
-            })
-    }
-
     pub fn guest_connection_ids(&self) -> Vec<ConnectionId> {
         self.guests.keys().copied().collect()
     }
@@ -1287,9 +1170,3 @@ impl Project {
             .collect()
     }
 }
-
-impl Channel {
-    fn connection_ids(&self) -> Vec<ConnectionId> {
-        self.connection_ids.iter().copied().collect()
-    }
-}

crates/zed/src/main.rs 🔗

@@ -115,7 +115,6 @@ fn main() {
 
         context_menu::init(cx);
         project::Project::init(&client);
-        client::Channel::init(&client);
         client::init(client.clone(), cx);
         command_palette::init(cx);
         editor::init(cx);