db.rs

  1mod ids;
  2mod queries;
  3mod tables;
  4#[cfg(test)]
  5pub mod tests;
  6
  7use crate::{executor::Executor, Error, Result};
  8use anyhow::anyhow;
  9use collections::{BTreeMap, HashMap, HashSet};
 10use dashmap::DashMap;
 11use futures::StreamExt;
 12use rand::{prelude::StdRng, Rng, SeedableRng};
 13use rpc::{
 14    proto::{self},
 15    ConnectionId, ExtensionMetadata,
 16};
 17use sea_orm::{
 18    entity::prelude::*,
 19    sea_query::{Alias, Expr, OnConflict},
 20    ActiveValue, Condition, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbErr,
 21    FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect, Statement,
 22    TransactionTrait,
 23};
 24use semantic_version::SemanticVersion;
 25use serde::{Deserialize, Serialize};
 26use sqlx::{
 27    migrate::{Migrate, Migration, MigrationSource},
 28    Connection,
 29};
 30use std::ops::RangeInclusive;
 31use std::{
 32    fmt::Write as _,
 33    future::Future,
 34    marker::PhantomData,
 35    ops::{Deref, DerefMut},
 36    path::Path,
 37    rc::Rc,
 38    sync::Arc,
 39    time::Duration,
 40};
 41use time::PrimitiveDateTime;
 42use tokio::sync::{Mutex, OwnedMutexGuard};
 43
 44#[cfg(test)]
 45pub use tests::TestDb;
 46
 47pub use ids::*;
 48pub use queries::billing_customers::{CreateBillingCustomerParams, UpdateBillingCustomerParams};
 49pub use queries::billing_subscriptions::{
 50    CreateBillingSubscriptionParams, UpdateBillingSubscriptionParams,
 51};
 52pub use queries::contributors::ContributorSelector;
 53pub use sea_orm::ConnectOptions;
 54pub use tables::user::Model as User;
 55pub use tables::*;
 56
 57/// Database gives you a handle that lets you access the database.
 58/// It handles pooling internally.
 59pub struct Database {
 60    options: ConnectOptions,
 61    pool: DatabaseConnection,
 62    rooms: DashMap<RoomId, Arc<Mutex<()>>>,
 63    projects: DashMap<ProjectId, Arc<Mutex<()>>>,
 64    rng: Mutex<StdRng>,
 65    executor: Executor,
 66    notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
 67    notification_kinds_by_name: HashMap<String, NotificationKindId>,
 68    #[cfg(test)]
 69    runtime: Option<tokio::runtime::Runtime>,
 70}
 71
 72// The `Database` type has so many methods that its impl blocks are split into
 73// separate files in the `queries` folder.
 74impl Database {
 75    /// Connects to the database with the given options
 76    pub async fn new(options: ConnectOptions, executor: Executor) -> Result<Self> {
 77        sqlx::any::install_default_drivers();
 78        Ok(Self {
 79            options: options.clone(),
 80            pool: sea_orm::Database::connect(options).await?,
 81            rooms: DashMap::with_capacity(16384),
 82            projects: DashMap::with_capacity(16384),
 83            rng: Mutex::new(StdRng::seed_from_u64(0)),
 84            notification_kinds_by_id: HashMap::default(),
 85            notification_kinds_by_name: HashMap::default(),
 86            executor,
 87            #[cfg(test)]
 88            runtime: None,
 89        })
 90    }
 91
 92    #[cfg(test)]
 93    pub fn reset(&self) {
 94        self.rooms.clear();
 95        self.projects.clear();
 96    }
 97
 98    /// Runs the database migrations.
 99    pub async fn migrate(
100        &self,
101        migrations_path: &Path,
102        ignore_checksum_mismatch: bool,
103    ) -> anyhow::Result<Vec<(Migration, Duration)>> {
104        let migrations = MigrationSource::resolve(migrations_path)
105            .await
106            .map_err(|err| anyhow!("failed to load migrations: {err:?}"))?;
107
108        let mut connection = sqlx::AnyConnection::connect(self.options.get_url()).await?;
109
110        connection.ensure_migrations_table().await?;
111        let applied_migrations: HashMap<_, _> = connection
112            .list_applied_migrations()
113            .await?
114            .into_iter()
115            .map(|m| (m.version, m))
116            .collect();
117
118        let mut new_migrations = Vec::new();
119        for migration in migrations {
120            match applied_migrations.get(&migration.version) {
121                Some(applied_migration) => {
122                    if migration.checksum != applied_migration.checksum && !ignore_checksum_mismatch
123                    {
124                        Err(anyhow!(
125                            "checksum mismatch for applied migration {}",
126                            migration.description
127                        ))?;
128                    }
129                }
130                None => {
131                    let elapsed = connection.apply(&migration).await?;
132                    new_migrations.push((migration, elapsed));
133                }
134            }
135        }
136
137        Ok(new_migrations)
138    }
139
140    /// Transaction runs things in a transaction. If you want to call other methods
141    /// and pass the transaction around you need to reborrow the transaction at each
142    /// call site with: `&*tx`.
143    pub async fn transaction<F, Fut, T>(&self, f: F) -> Result<T>
144    where
145        F: Send + Fn(TransactionHandle) -> Fut,
146        Fut: Send + Future<Output = Result<T>>,
147    {
148        let body = async {
149            let mut i = 0;
150            loop {
151                let (tx, result) = self.with_transaction(&f).await?;
152                match result {
153                    Ok(result) => match tx.commit().await.map_err(Into::into) {
154                        Ok(()) => return Ok(result),
155                        Err(error) => {
156                            if !self.retry_on_serialization_error(&error, i).await {
157                                return Err(error);
158                            }
159                        }
160                    },
161                    Err(error) => {
162                        tx.rollback().await?;
163                        if !self.retry_on_serialization_error(&error, i).await {
164                            return Err(error);
165                        }
166                    }
167                }
168                i += 1;
169            }
170        };
171
172        self.run(body).await
173    }
174
175    pub async fn weak_transaction<F, Fut, T>(&self, f: F) -> Result<T>
176    where
177        F: Send + Fn(TransactionHandle) -> Fut,
178        Fut: Send + Future<Output = Result<T>>,
179    {
180        let body = async {
181            let (tx, result) = self.with_weak_transaction(&f).await?;
182            match result {
183                Ok(result) => match tx.commit().await.map_err(Into::into) {
184                    Ok(()) => return Ok(result),
185                    Err(error) => {
186                        return Err(error);
187                    }
188                },
189                Err(error) => {
190                    tx.rollback().await?;
191                    return Err(error);
192                }
193            }
194        };
195
196        self.run(body).await
197    }
198
199    /// The same as room_transaction, but if you need to only optionally return a Room.
200    async fn optional_room_transaction<F, Fut, T>(
201        &self,
202        f: F,
203    ) -> Result<Option<TransactionGuard<T>>>
204    where
205        F: Send + Fn(TransactionHandle) -> Fut,
206        Fut: Send + Future<Output = Result<Option<(RoomId, T)>>>,
207    {
208        let body = async {
209            let mut i = 0;
210            loop {
211                let (tx, result) = self.with_transaction(&f).await?;
212                match result {
213                    Ok(Some((room_id, data))) => {
214                        let lock = self.rooms.entry(room_id).or_default().clone();
215                        let _guard = lock.lock_owned().await;
216                        match tx.commit().await.map_err(Into::into) {
217                            Ok(()) => {
218                                return Ok(Some(TransactionGuard {
219                                    data,
220                                    _guard,
221                                    _not_send: PhantomData,
222                                }));
223                            }
224                            Err(error) => {
225                                if !self.retry_on_serialization_error(&error, i).await {
226                                    return Err(error);
227                                }
228                            }
229                        }
230                    }
231                    Ok(None) => match tx.commit().await.map_err(Into::into) {
232                        Ok(()) => return Ok(None),
233                        Err(error) => {
234                            if !self.retry_on_serialization_error(&error, i).await {
235                                return Err(error);
236                            }
237                        }
238                    },
239                    Err(error) => {
240                        tx.rollback().await?;
241                        if !self.retry_on_serialization_error(&error, i).await {
242                            return Err(error);
243                        }
244                    }
245                }
246                i += 1;
247            }
248        };
249
250        self.run(body).await
251    }
252
253    async fn project_transaction<F, Fut, T>(
254        &self,
255        project_id: ProjectId,
256        f: F,
257    ) -> Result<TransactionGuard<T>>
258    where
259        F: Send + Fn(TransactionHandle) -> Fut,
260        Fut: Send + Future<Output = Result<T>>,
261    {
262        let room_id = Database::room_id_for_project(&self, project_id).await?;
263        let body = async {
264            let mut i = 0;
265            loop {
266                let lock = if let Some(room_id) = room_id {
267                    self.rooms.entry(room_id).or_default().clone()
268                } else {
269                    self.projects.entry(project_id).or_default().clone()
270                };
271                let _guard = lock.lock_owned().await;
272                let (tx, result) = self.with_transaction(&f).await?;
273                match result {
274                    Ok(data) => match tx.commit().await.map_err(Into::into) {
275                        Ok(()) => {
276                            return Ok(TransactionGuard {
277                                data,
278                                _guard,
279                                _not_send: PhantomData,
280                            });
281                        }
282                        Err(error) => {
283                            if !self.retry_on_serialization_error(&error, i).await {
284                                return Err(error);
285                            }
286                        }
287                    },
288                    Err(error) => {
289                        tx.rollback().await?;
290                        if !self.retry_on_serialization_error(&error, i).await {
291                            return Err(error);
292                        }
293                    }
294                }
295                i += 1;
296            }
297        };
298
299        self.run(body).await
300    }
301
302    /// room_transaction runs the block in a transaction. It returns a RoomGuard, that keeps
303    /// the database locked until it is dropped. This ensures that updates sent to clients are
304    /// properly serialized with respect to database changes.
305    async fn room_transaction<F, Fut, T>(
306        &self,
307        room_id: RoomId,
308        f: F,
309    ) -> Result<TransactionGuard<T>>
310    where
311        F: Send + Fn(TransactionHandle) -> Fut,
312        Fut: Send + Future<Output = Result<T>>,
313    {
314        let body = async {
315            let mut i = 0;
316            loop {
317                let lock = self.rooms.entry(room_id).or_default().clone();
318                let _guard = lock.lock_owned().await;
319                let (tx, result) = self.with_transaction(&f).await?;
320                match result {
321                    Ok(data) => match tx.commit().await.map_err(Into::into) {
322                        Ok(()) => {
323                            return Ok(TransactionGuard {
324                                data,
325                                _guard,
326                                _not_send: PhantomData,
327                            });
328                        }
329                        Err(error) => {
330                            if !self.retry_on_serialization_error(&error, i).await {
331                                return Err(error);
332                            }
333                        }
334                    },
335                    Err(error) => {
336                        tx.rollback().await?;
337                        if !self.retry_on_serialization_error(&error, i).await {
338                            return Err(error);
339                        }
340                    }
341                }
342                i += 1;
343            }
344        };
345
346        self.run(body).await
347    }
348
349    async fn with_transaction<F, Fut, T>(&self, f: &F) -> Result<(DatabaseTransaction, Result<T>)>
350    where
351        F: Send + Fn(TransactionHandle) -> Fut,
352        Fut: Send + Future<Output = Result<T>>,
353    {
354        let tx = self
355            .pool
356            .begin_with_config(Some(IsolationLevel::Serializable), None)
357            .await?;
358
359        let mut tx = Arc::new(Some(tx));
360        let result = f(TransactionHandle(tx.clone())).await;
361        let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
362            return Err(anyhow!(
363                "couldn't complete transaction because it's still in use"
364            ))?;
365        };
366
367        Ok((tx, result))
368    }
369
370    async fn with_weak_transaction<F, Fut, T>(
371        &self,
372        f: &F,
373    ) -> Result<(DatabaseTransaction, Result<T>)>
374    where
375        F: Send + Fn(TransactionHandle) -> Fut,
376        Fut: Send + Future<Output = Result<T>>,
377    {
378        let tx = self
379            .pool
380            .begin_with_config(Some(IsolationLevel::ReadCommitted), None)
381            .await?;
382
383        let mut tx = Arc::new(Some(tx));
384        let result = f(TransactionHandle(tx.clone())).await;
385        let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
386            return Err(anyhow!(
387                "couldn't complete transaction because it's still in use"
388            ))?;
389        };
390
391        Ok((tx, result))
392    }
393
394    async fn run<F, T>(&self, future: F) -> Result<T>
395    where
396        F: Future<Output = Result<T>>,
397    {
398        #[cfg(test)]
399        {
400            if let Executor::Deterministic(executor) = &self.executor {
401                executor.simulate_random_delay().await;
402            }
403
404            self.runtime.as_ref().unwrap().block_on(future)
405        }
406
407        #[cfg(not(test))]
408        {
409            future.await
410        }
411    }
412
413    async fn retry_on_serialization_error(&self, error: &Error, prev_attempt_count: usize) -> bool {
414        // If the error is due to a failure to serialize concurrent transactions, then retry
415        // this transaction after a delay. With each subsequent retry, double the delay duration.
416        // Also vary the delay randomly in order to ensure different database connections retry
417        // at different times.
418        const SLEEPS: [f32; 10] = [10., 20., 40., 80., 160., 320., 640., 1280., 2560., 5120.];
419        if is_serialization_error(error) && prev_attempt_count < SLEEPS.len() {
420            let base_delay = SLEEPS[prev_attempt_count];
421            let randomized_delay = base_delay * self.rng.lock().await.gen_range(0.5..=2.0);
422            log::warn!(
423                "retrying transaction after serialization error. delay: {} ms.",
424                randomized_delay
425            );
426            self.executor
427                .sleep(Duration::from_millis(randomized_delay as u64))
428                .await;
429            true
430        } else {
431            false
432        }
433    }
434}
435
436fn is_serialization_error(error: &Error) -> bool {
437    const SERIALIZATION_FAILURE_CODE: &str = "40001";
438    match error {
439        Error::Database(
440            DbErr::Exec(sea_orm::RuntimeErr::SqlxError(error))
441            | DbErr::Query(sea_orm::RuntimeErr::SqlxError(error)),
442        ) if error
443            .as_database_error()
444            .and_then(|error| error.code())
445            .as_deref()
446            == Some(SERIALIZATION_FAILURE_CODE) =>
447        {
448            true
449        }
450        _ => false,
451    }
452}
453
454/// A handle to a [`DatabaseTransaction`].
455pub struct TransactionHandle(Arc<Option<DatabaseTransaction>>);
456
457impl Deref for TransactionHandle {
458    type Target = DatabaseTransaction;
459
460    fn deref(&self) -> &Self::Target {
461        self.0.as_ref().as_ref().unwrap()
462    }
463}
464
465/// [`TransactionGuard`] keeps a database transaction alive until it is dropped.
466/// It wraps data that depends on the state of the database and prevents an additional
467/// transaction from starting that would invalidate that data.
468pub struct TransactionGuard<T> {
469    data: T,
470    _guard: OwnedMutexGuard<()>,
471    _not_send: PhantomData<Rc<()>>,
472}
473
474impl<T> Deref for TransactionGuard<T> {
475    type Target = T;
476
477    fn deref(&self) -> &T {
478        &self.data
479    }
480}
481
482impl<T> DerefMut for TransactionGuard<T> {
483    fn deref_mut(&mut self) -> &mut T {
484        &mut self.data
485    }
486}
487
488impl<T> TransactionGuard<T> {
489    /// Returns the inner value of the guard.
490    pub fn into_inner(self) -> T {
491        self.data
492    }
493}
494
495#[derive(Clone, Debug, PartialEq, Eq)]
496pub enum Contact {
497    Accepted { user_id: UserId, busy: bool },
498    Outgoing { user_id: UserId },
499    Incoming { user_id: UserId },
500}
501
502impl Contact {
503    pub fn user_id(&self) -> UserId {
504        match self {
505            Contact::Accepted { user_id, .. } => *user_id,
506            Contact::Outgoing { user_id } => *user_id,
507            Contact::Incoming { user_id, .. } => *user_id,
508        }
509    }
510}
511
512pub type NotificationBatch = Vec<(UserId, proto::Notification)>;
513
514pub struct CreatedChannelMessage {
515    pub message_id: MessageId,
516    pub participant_connection_ids: HashSet<ConnectionId>,
517    pub notifications: NotificationBatch,
518}
519
520pub struct UpdatedChannelMessage {
521    pub message_id: MessageId,
522    pub participant_connection_ids: Vec<ConnectionId>,
523    pub notifications: NotificationBatch,
524    pub reply_to_message_id: Option<MessageId>,
525    pub timestamp: PrimitiveDateTime,
526    pub deleted_mention_notification_ids: Vec<NotificationId>,
527    pub updated_mention_notifications: Vec<rpc::proto::Notification>,
528}
529
530#[derive(Clone, Debug, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
531pub struct Invite {
532    pub email_address: String,
533    pub email_confirmation_code: String,
534}
535
536#[derive(Clone, Debug, Deserialize)]
537pub struct NewSignup {
538    pub email_address: String,
539    pub platform_mac: bool,
540    pub platform_windows: bool,
541    pub platform_linux: bool,
542    pub editor_features: Vec<String>,
543    pub programming_languages: Vec<String>,
544    pub device_id: Option<String>,
545    pub added_to_mailing_list: bool,
546    pub created_at: Option<DateTime>,
547}
548
549#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromQueryResult)]
550pub struct WaitlistSummary {
551    pub count: i64,
552    pub linux_count: i64,
553    pub mac_count: i64,
554    pub windows_count: i64,
555    pub unknown_count: i64,
556}
557
558/// The parameters to create a new user.
559#[derive(Debug, Serialize, Deserialize)]
560pub struct NewUserParams {
561    pub github_login: String,
562    pub github_user_id: i32,
563}
564
565/// The result of creating a new user.
566#[derive(Debug)]
567pub struct NewUserResult {
568    pub user_id: UserId,
569    pub metrics_id: String,
570    pub inviting_user_id: Option<UserId>,
571    pub signup_device_id: Option<String>,
572}
573
574/// The result of updating a channel membership.
575#[derive(Debug)]
576pub struct MembershipUpdated {
577    pub channel_id: ChannelId,
578    pub new_channels: ChannelsForUser,
579    pub removed_channels: Vec<ChannelId>,
580}
581
582/// The result of setting a member's role.
583#[derive(Debug)]
584#[allow(clippy::large_enum_variant)]
585pub enum SetMemberRoleResult {
586    InviteUpdated(Channel),
587    MembershipUpdated(MembershipUpdated),
588}
589
590/// The result of inviting a member to a channel.
591#[derive(Debug)]
592pub struct InviteMemberResult {
593    pub channel: Channel,
594    pub notifications: NotificationBatch,
595}
596
597#[derive(Debug)]
598pub struct RespondToChannelInvite {
599    pub membership_update: Option<MembershipUpdated>,
600    pub notifications: NotificationBatch,
601}
602
603#[derive(Debug)]
604pub struct RemoveChannelMemberResult {
605    pub membership_update: MembershipUpdated,
606    pub notification_id: Option<NotificationId>,
607}
608
609#[derive(Debug, PartialEq, Eq, Hash)]
610pub struct Channel {
611    pub id: ChannelId,
612    pub name: String,
613    pub visibility: ChannelVisibility,
614    /// parent_path is the channel ids from the root to this one (not including this one)
615    pub parent_path: Vec<ChannelId>,
616}
617
618impl Channel {
619    pub fn from_model(value: channel::Model) -> Self {
620        Channel {
621            id: value.id,
622            visibility: value.visibility,
623            name: value.clone().name,
624            parent_path: value.ancestors().collect(),
625        }
626    }
627
628    pub fn to_proto(&self) -> proto::Channel {
629        proto::Channel {
630            id: self.id.to_proto(),
631            name: self.name.clone(),
632            visibility: self.visibility.into(),
633            parent_path: self.parent_path.iter().map(|c| c.to_proto()).collect(),
634        }
635    }
636}
637
638#[derive(Debug, PartialEq, Eq, Hash)]
639pub struct ChannelMember {
640    pub role: ChannelRole,
641    pub user_id: UserId,
642    pub kind: proto::channel_member::Kind,
643}
644
645impl ChannelMember {
646    pub fn to_proto(&self) -> proto::ChannelMember {
647        proto::ChannelMember {
648            role: self.role.into(),
649            user_id: self.user_id.to_proto(),
650            kind: self.kind.into(),
651        }
652    }
653}
654
655#[derive(Debug, PartialEq)]
656pub struct ChannelsForUser {
657    pub channels: Vec<Channel>,
658    pub channel_memberships: Vec<channel_member::Model>,
659    pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
660    pub hosted_projects: Vec<proto::HostedProject>,
661    pub invited_channels: Vec<Channel>,
662
663    pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
664    pub observed_channel_messages: Vec<proto::ChannelMessageId>,
665    pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
666    pub latest_channel_messages: Vec<proto::ChannelMessageId>,
667}
668
669#[derive(Debug)]
670pub struct RejoinedChannelBuffer {
671    pub buffer: proto::RejoinedChannelBuffer,
672    pub old_connection_id: ConnectionId,
673}
674
675#[derive(Clone)]
676pub struct JoinRoom {
677    pub room: proto::Room,
678    pub channel: Option<channel::Model>,
679}
680
681pub struct RejoinedRoom {
682    pub room: proto::Room,
683    pub rejoined_projects: Vec<RejoinedProject>,
684    pub reshared_projects: Vec<ResharedProject>,
685    pub channel: Option<channel::Model>,
686}
687
688pub struct ResharedProject {
689    pub id: ProjectId,
690    pub old_connection_id: ConnectionId,
691    pub collaborators: Vec<ProjectCollaborator>,
692    pub worktrees: Vec<proto::WorktreeMetadata>,
693}
694
695pub struct RejoinedProject {
696    pub id: ProjectId,
697    pub old_connection_id: ConnectionId,
698    pub collaborators: Vec<ProjectCollaborator>,
699    pub worktrees: Vec<RejoinedWorktree>,
700    pub language_servers: Vec<proto::LanguageServer>,
701}
702
703impl RejoinedProject {
704    pub fn to_proto(&self) -> proto::RejoinedProject {
705        proto::RejoinedProject {
706            id: self.id.to_proto(),
707            worktrees: self
708                .worktrees
709                .iter()
710                .map(|worktree| proto::WorktreeMetadata {
711                    id: worktree.id,
712                    root_name: worktree.root_name.clone(),
713                    visible: worktree.visible,
714                    abs_path: worktree.abs_path.clone(),
715                })
716                .collect(),
717            collaborators: self
718                .collaborators
719                .iter()
720                .map(|collaborator| collaborator.to_proto())
721                .collect(),
722            language_servers: self.language_servers.clone(),
723        }
724    }
725}
726
727#[derive(Debug)]
728pub struct RejoinedWorktree {
729    pub id: u64,
730    pub abs_path: String,
731    pub root_name: String,
732    pub visible: bool,
733    pub updated_entries: Vec<proto::Entry>,
734    pub removed_entries: Vec<u64>,
735    pub updated_repositories: Vec<proto::RepositoryEntry>,
736    pub removed_repositories: Vec<u64>,
737    pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
738    pub settings_files: Vec<WorktreeSettingsFile>,
739    pub scan_id: u64,
740    pub completed_scan_id: u64,
741}
742
743pub struct LeftRoom {
744    pub room: proto::Room,
745    pub channel: Option<channel::Model>,
746    pub left_projects: HashMap<ProjectId, LeftProject>,
747    pub canceled_calls_to_user_ids: Vec<UserId>,
748    pub deleted: bool,
749}
750
751pub struct RefreshedRoom {
752    pub room: proto::Room,
753    pub channel: Option<channel::Model>,
754    pub stale_participant_user_ids: Vec<UserId>,
755    pub canceled_calls_to_user_ids: Vec<UserId>,
756}
757
758pub struct RefreshedChannelBuffer {
759    pub connection_ids: Vec<ConnectionId>,
760    pub collaborators: Vec<proto::Collaborator>,
761}
762
763pub struct Project {
764    pub id: ProjectId,
765    pub role: ChannelRole,
766    pub collaborators: Vec<ProjectCollaborator>,
767    pub worktrees: BTreeMap<u64, Worktree>,
768    pub language_servers: Vec<proto::LanguageServer>,
769    pub dev_server_project_id: Option<DevServerProjectId>,
770}
771
772pub struct ProjectCollaborator {
773    pub connection_id: ConnectionId,
774    pub user_id: UserId,
775    pub replica_id: ReplicaId,
776    pub is_host: bool,
777}
778
779impl ProjectCollaborator {
780    pub fn to_proto(&self) -> proto::Collaborator {
781        proto::Collaborator {
782            peer_id: Some(self.connection_id.into()),
783            replica_id: self.replica_id.0 as u32,
784            user_id: self.user_id.to_proto(),
785        }
786    }
787}
788
789#[derive(Debug)]
790pub struct LeftProject {
791    pub id: ProjectId,
792    pub should_unshare: bool,
793    pub connection_ids: Vec<ConnectionId>,
794}
795
796pub struct Worktree {
797    pub id: u64,
798    pub abs_path: String,
799    pub root_name: String,
800    pub visible: bool,
801    pub entries: Vec<proto::Entry>,
802    pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
803    pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
804    pub settings_files: Vec<WorktreeSettingsFile>,
805    pub scan_id: u64,
806    pub completed_scan_id: u64,
807}
808
809#[derive(Debug)]
810pub struct WorktreeSettingsFile {
811    pub path: String,
812    pub content: String,
813}
814
815pub struct NewExtensionVersion {
816    pub name: String,
817    pub version: semver::Version,
818    pub description: String,
819    pub authors: Vec<String>,
820    pub repository: String,
821    pub schema_version: i32,
822    pub wasm_api_version: Option<String>,
823    pub published_at: PrimitiveDateTime,
824}
825
826pub struct ExtensionVersionConstraints {
827    pub schema_versions: RangeInclusive<i32>,
828    pub wasm_api_versions: RangeInclusive<SemanticVersion>,
829}