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