remote_projects.rs

  1use anyhow::anyhow;
  2use rpc::{proto, ConnectionId};
  3use sea_orm::{
  4    ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseTransaction, EntityTrait,
  5    ModelTrait, QueryFilter,
  6};
  7
  8use crate::db::ProjectId;
  9
 10use super::{
 11    channel, project, project_collaborator, remote_project, worktree, ChannelId, Database,
 12    DevServerId, RejoinedProject, RemoteProjectId, ResharedProject, ServerId, UserId,
 13};
 14
 15impl Database {
 16    pub async fn get_remote_project(
 17        &self,
 18        remote_project_id: RemoteProjectId,
 19    ) -> crate::Result<remote_project::Model> {
 20        self.transaction(|tx| async move {
 21            Ok(remote_project::Entity::find_by_id(remote_project_id)
 22                .one(&*tx)
 23                .await?
 24                .ok_or_else(|| anyhow!("no remote project with id {}", remote_project_id))?)
 25        })
 26        .await
 27    }
 28
 29    pub async fn get_remote_projects(
 30        &self,
 31        channel_ids: &Vec<ChannelId>,
 32        tx: &DatabaseTransaction,
 33    ) -> crate::Result<Vec<proto::RemoteProject>> {
 34        let servers = remote_project::Entity::find()
 35            .filter(remote_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0)))
 36            .find_also_related(project::Entity)
 37            .all(tx)
 38            .await?;
 39        Ok(servers
 40            .into_iter()
 41            .map(|(remote_project, project)| proto::RemoteProject {
 42                id: remote_project.id.to_proto(),
 43                project_id: project.map(|p| p.id.to_proto()),
 44                channel_id: remote_project.channel_id.to_proto(),
 45                name: remote_project.name,
 46                dev_server_id: remote_project.dev_server_id.to_proto(),
 47                path: remote_project.path,
 48            })
 49            .collect())
 50    }
 51
 52    pub async fn get_remote_projects_for_dev_server(
 53        &self,
 54        dev_server_id: DevServerId,
 55    ) -> crate::Result<Vec<proto::RemoteProject>> {
 56        self.transaction(|tx| async move {
 57            let servers = remote_project::Entity::find()
 58                .filter(remote_project::Column::DevServerId.eq(dev_server_id))
 59                .find_also_related(project::Entity)
 60                .all(&*tx)
 61                .await?;
 62            Ok(servers
 63                .into_iter()
 64                .map(|(remote_project, project)| proto::RemoteProject {
 65                    id: remote_project.id.to_proto(),
 66                    project_id: project.map(|p| p.id.to_proto()),
 67                    channel_id: remote_project.channel_id.to_proto(),
 68                    name: remote_project.name,
 69                    dev_server_id: remote_project.dev_server_id.to_proto(),
 70                    path: remote_project.path,
 71                })
 72                .collect())
 73        })
 74        .await
 75    }
 76
 77    pub async fn get_stale_dev_server_projects(
 78        &self,
 79        connection: ConnectionId,
 80    ) -> crate::Result<Vec<ProjectId>> {
 81        self.transaction(|tx| async move {
 82            let projects = project::Entity::find()
 83                .filter(
 84                    Condition::all()
 85                        .add(project::Column::HostConnectionId.eq(connection.id))
 86                        .add(project::Column::HostConnectionServerId.eq(connection.owner_id)),
 87                )
 88                .all(&*tx)
 89                .await?;
 90
 91            Ok(projects.into_iter().map(|p| p.id).collect())
 92        })
 93        .await
 94    }
 95
 96    pub async fn create_remote_project(
 97        &self,
 98        channel_id: ChannelId,
 99        dev_server_id: DevServerId,
100        name: &str,
101        path: &str,
102        user_id: UserId,
103    ) -> crate::Result<(channel::Model, remote_project::Model)> {
104        self.transaction(|tx| async move {
105            let channel = self.get_channel_internal(channel_id, &tx).await?;
106            self.check_user_is_channel_admin(&channel, user_id, &tx)
107                .await?;
108
109            let project = remote_project::Entity::insert(remote_project::ActiveModel {
110                name: ActiveValue::Set(name.to_string()),
111                id: ActiveValue::NotSet,
112                channel_id: ActiveValue::Set(channel_id),
113                dev_server_id: ActiveValue::Set(dev_server_id),
114                path: ActiveValue::Set(path.to_string()),
115            })
116            .exec_with_returning(&*tx)
117            .await?;
118
119            Ok((channel, project))
120        })
121        .await
122    }
123
124    pub async fn share_remote_project(
125        &self,
126        remote_project_id: RemoteProjectId,
127        dev_server_id: DevServerId,
128        connection: ConnectionId,
129        worktrees: &[proto::WorktreeMetadata],
130    ) -> crate::Result<proto::RemoteProject> {
131        self.transaction(|tx| async move {
132            let remote_project = remote_project::Entity::find_by_id(remote_project_id)
133                .one(&*tx)
134                .await?
135                .ok_or_else(|| anyhow!("no remote project with id {}", remote_project_id))?;
136
137            if remote_project.dev_server_id != dev_server_id {
138                return Err(anyhow!("remote project shared from wrong server"))?;
139            }
140
141            let project = project::ActiveModel {
142                room_id: ActiveValue::Set(None),
143                host_user_id: ActiveValue::Set(None),
144                host_connection_id: ActiveValue::set(Some(connection.id as i32)),
145                host_connection_server_id: ActiveValue::set(Some(ServerId(
146                    connection.owner_id as i32,
147                ))),
148                id: ActiveValue::NotSet,
149                hosted_project_id: ActiveValue::Set(None),
150                remote_project_id: ActiveValue::Set(Some(remote_project_id)),
151            }
152            .insert(&*tx)
153            .await?;
154
155            if !worktrees.is_empty() {
156                worktree::Entity::insert_many(worktrees.iter().map(|worktree| {
157                    worktree::ActiveModel {
158                        id: ActiveValue::set(worktree.id as i64),
159                        project_id: ActiveValue::set(project.id),
160                        abs_path: ActiveValue::set(worktree.abs_path.clone()),
161                        root_name: ActiveValue::set(worktree.root_name.clone()),
162                        visible: ActiveValue::set(worktree.visible),
163                        scan_id: ActiveValue::set(0),
164                        completed_scan_id: ActiveValue::set(0),
165                    }
166                }))
167                .exec(&*tx)
168                .await?;
169            }
170
171            Ok(remote_project.to_proto(Some(project)))
172        })
173        .await
174    }
175
176    pub async fn reshare_remote_projects(
177        &self,
178        reshared_projects: &Vec<proto::UpdateProject>,
179        dev_server_id: DevServerId,
180        connection: ConnectionId,
181    ) -> crate::Result<Vec<ResharedProject>> {
182        // todo!() project_transaction? (maybe we can make the lock per-dev-server instead of per-project?)
183        self.transaction(|tx| async move {
184            let mut ret = Vec::new();
185            for reshared_project in reshared_projects {
186                let project_id = ProjectId::from_proto(reshared_project.project_id);
187                let (project, remote_project) = project::Entity::find_by_id(project_id)
188                    .find_also_related(remote_project::Entity)
189                    .one(&*tx)
190                    .await?
191                    .ok_or_else(|| anyhow!("project does not exist"))?;
192
193                if remote_project.map(|rp| rp.dev_server_id) != Some(dev_server_id) {
194                    return Err(anyhow!("remote project reshared from wrong server"))?;
195                }
196
197                let Ok(old_connection_id) = project.host_connection() else {
198                    return Err(anyhow!("remote project was not shared"))?;
199                };
200
201                project::Entity::update(project::ActiveModel {
202                    id: ActiveValue::set(project_id),
203                    host_connection_id: ActiveValue::set(Some(connection.id as i32)),
204                    host_connection_server_id: ActiveValue::set(Some(ServerId(
205                        connection.owner_id as i32,
206                    ))),
207                    ..Default::default()
208                })
209                .exec(&*tx)
210                .await?;
211
212                let collaborators = project
213                    .find_related(project_collaborator::Entity)
214                    .all(&*tx)
215                    .await?;
216
217                self.update_project_worktrees(project_id, &reshared_project.worktrees, &tx)
218                    .await?;
219
220                ret.push(super::ResharedProject {
221                    id: project_id,
222                    old_connection_id,
223                    collaborators: collaborators
224                        .iter()
225                        .map(|collaborator| super::ProjectCollaborator {
226                            connection_id: collaborator.connection(),
227                            user_id: collaborator.user_id,
228                            replica_id: collaborator.replica_id,
229                            is_host: collaborator.is_host,
230                        })
231                        .collect(),
232                    worktrees: reshared_project.worktrees.clone(),
233                });
234            }
235            Ok(ret)
236        })
237        .await
238    }
239
240    pub async fn rejoin_remote_projects(
241        &self,
242        rejoined_projects: &Vec<proto::RejoinProject>,
243        user_id: UserId,
244        connection_id: ConnectionId,
245    ) -> crate::Result<Vec<RejoinedProject>> {
246        // todo!() project_transaction? (maybe we can make the lock per-dev-server instead of per-project?)
247        self.transaction(|tx| async move {
248            let mut ret = Vec::new();
249            for rejoined_project in rejoined_projects {
250                if let Some(project) = self
251                    .rejoin_project_internal(&tx, rejoined_project, user_id, connection_id)
252                    .await?
253                {
254                    ret.push(project);
255                }
256            }
257            Ok(ret)
258        })
259        .await
260    }
261}