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}