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