dev_server_projects.rs

  1use anyhow::anyhow;
  2use rpc::{
  3    proto::{self},
  4    ConnectionId,
  5};
  6use sea_orm::{
  7    ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseTransaction, EntityTrait,
  8    IntoActiveModel, ModelTrait, QueryFilter,
  9};
 10
 11use crate::db::ProjectId;
 12
 13use super::{
 14    dev_server, dev_server_project, project, project_collaborator, worktree, Database, DevServerId,
 15    DevServerProjectId, RejoinedProject, ResharedProject, ServerId, UserId,
 16};
 17
 18impl Database {
 19    pub async fn get_dev_server_project(
 20        &self,
 21        dev_server_project_id: DevServerProjectId,
 22    ) -> crate::Result<dev_server_project::Model> {
 23        self.transaction(|tx| async move {
 24            Ok(
 25                dev_server_project::Entity::find_by_id(dev_server_project_id)
 26                    .one(&*tx)
 27                    .await?
 28                    .ok_or_else(|| {
 29                        anyhow!("no dev server project with id {}", dev_server_project_id)
 30                    })?,
 31            )
 32        })
 33        .await
 34    }
 35
 36    pub async fn get_projects_for_dev_server(
 37        &self,
 38        dev_server_id: DevServerId,
 39    ) -> crate::Result<Vec<proto::DevServerProject>> {
 40        self.transaction(|tx| async move {
 41            self.get_projects_for_dev_server_internal(dev_server_id, &tx)
 42                .await
 43        })
 44        .await
 45    }
 46
 47    pub async fn get_projects_for_dev_server_internal(
 48        &self,
 49        dev_server_id: DevServerId,
 50        tx: &DatabaseTransaction,
 51    ) -> crate::Result<Vec<proto::DevServerProject>> {
 52        let servers = dev_server_project::Entity::find()
 53            .filter(dev_server_project::Column::DevServerId.eq(dev_server_id))
 54            .find_also_related(project::Entity)
 55            .all(tx)
 56            .await?;
 57        Ok(servers
 58            .into_iter()
 59            .map(|(dev_server_project, project)| dev_server_project.to_proto(project))
 60            .collect())
 61    }
 62
 63    pub async fn dev_server_project_ids_for_user(
 64        &self,
 65        user_id: UserId,
 66        tx: &DatabaseTransaction,
 67    ) -> crate::Result<Vec<DevServerProjectId>> {
 68        let dev_servers = dev_server::Entity::find()
 69            .filter(dev_server::Column::UserId.eq(user_id))
 70            .find_with_related(dev_server_project::Entity)
 71            .all(tx)
 72            .await?;
 73
 74        Ok(dev_servers
 75            .into_iter()
 76            .flat_map(|(_, projects)| projects.into_iter().map(|p| p.id))
 77            .collect())
 78    }
 79
 80    pub async fn owner_for_dev_server_project(
 81        &self,
 82        dev_server_project_id: DevServerProjectId,
 83        tx: &DatabaseTransaction,
 84    ) -> crate::Result<UserId> {
 85        let dev_server = dev_server_project::Entity::find_by_id(dev_server_project_id)
 86            .find_also_related(dev_server::Entity)
 87            .one(tx)
 88            .await?
 89            .and_then(|(_, dev_server)| dev_server)
 90            .ok_or_else(|| anyhow!("no dev server project"))?;
 91
 92        Ok(dev_server.user_id)
 93    }
 94
 95    pub async fn get_stale_dev_server_projects(
 96        &self,
 97        connection: ConnectionId,
 98    ) -> crate::Result<Vec<ProjectId>> {
 99        self.transaction(|tx| async move {
100            let projects = project::Entity::find()
101                .filter(
102                    Condition::all()
103                        .add(project::Column::HostConnectionId.eq(connection.id))
104                        .add(project::Column::HostConnectionServerId.eq(connection.owner_id)),
105                )
106                .all(&*tx)
107                .await?;
108
109            Ok(projects.into_iter().map(|p| p.id).collect())
110        })
111        .await
112    }
113
114    pub async fn create_dev_server_project(
115        &self,
116        dev_server_id: DevServerId,
117        path: &str,
118        user_id: UserId,
119    ) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
120        self.transaction(|tx| async move {
121            let dev_server = dev_server::Entity::find_by_id(dev_server_id)
122                .one(&*tx)
123                .await?
124                .ok_or_else(|| anyhow!("no dev server with id {}", dev_server_id))?;
125            if dev_server.user_id != user_id {
126                return Err(anyhow!("not your dev server"))?;
127            }
128
129            let project = dev_server_project::Entity::insert(dev_server_project::ActiveModel {
130                id: ActiveValue::NotSet,
131                dev_server_id: ActiveValue::Set(dev_server_id),
132                paths: ActiveValue::Set(dev_server_project::JSONPaths(vec![path.to_string()])),
133            })
134            .exec_with_returning(&*tx)
135            .await?;
136
137            let status = self
138                .dev_server_projects_update_internal(user_id, &tx)
139                .await?;
140
141            Ok((project, status))
142        })
143        .await
144    }
145
146    pub async fn update_dev_server_project(
147        &self,
148        id: DevServerProjectId,
149        paths: &Vec<String>,
150        user_id: UserId,
151    ) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
152        self.transaction(move |tx| async move {
153            let paths = paths.clone();
154            let Some((project, Some(dev_server))) = dev_server_project::Entity::find_by_id(id)
155                .find_also_related(dev_server::Entity)
156                .one(&*tx)
157                .await?
158            else {
159                return Err(anyhow!("no such dev server project"))?;
160            };
161
162            if dev_server.user_id != user_id {
163                return Err(anyhow!("not your dev server"))?;
164            }
165            let mut project = project.into_active_model();
166            project.paths = ActiveValue::Set(dev_server_project::JSONPaths(paths));
167            let project = project.update(&*tx).await?;
168
169            let status = self
170                .dev_server_projects_update_internal(user_id, &tx)
171                .await?;
172
173            Ok((project, status))
174        })
175        .await
176    }
177
178    pub async fn delete_dev_server_project(
179        &self,
180        dev_server_project_id: DevServerProjectId,
181        dev_server_id: DevServerId,
182        user_id: UserId,
183    ) -> crate::Result<(Vec<proto::DevServerProject>, proto::DevServerProjectsUpdate)> {
184        self.transaction(|tx| async move {
185            project::Entity::delete_many()
186                .filter(project::Column::DevServerProjectId.eq(dev_server_project_id))
187                .exec(&*tx)
188                .await?;
189            let result = dev_server_project::Entity::delete_by_id(dev_server_project_id)
190                .exec(&*tx)
191                .await?;
192            if result.rows_affected != 1 {
193                return Err(anyhow!(
194                    "no dev server project with id {}",
195                    dev_server_project_id
196                ))?;
197            }
198
199            let status = self
200                .dev_server_projects_update_internal(user_id, &tx)
201                .await?;
202
203            let projects = self
204                .get_projects_for_dev_server_internal(dev_server_id, &tx)
205                .await?;
206            Ok((projects, status))
207        })
208        .await
209    }
210
211    pub async fn share_dev_server_project(
212        &self,
213        dev_server_project_id: DevServerProjectId,
214        dev_server_id: DevServerId,
215        connection: ConnectionId,
216        worktrees: &[proto::WorktreeMetadata],
217    ) -> crate::Result<(
218        proto::DevServerProject,
219        UserId,
220        proto::DevServerProjectsUpdate,
221    )> {
222        self.transaction(|tx| async move {
223            let dev_server = dev_server::Entity::find_by_id(dev_server_id)
224                .one(&*tx)
225                .await?
226                .ok_or_else(|| anyhow!("no dev server with id {}", dev_server_id))?;
227
228            let dev_server_project = dev_server_project::Entity::find_by_id(dev_server_project_id)
229                .one(&*tx)
230                .await?
231                .ok_or_else(|| {
232                    anyhow!("no dev server project with id {}", dev_server_project_id)
233                })?;
234
235            if dev_server_project.dev_server_id != dev_server_id {
236                return Err(anyhow!("dev server project shared from wrong server"))?;
237            }
238
239            let project = project::ActiveModel {
240                room_id: ActiveValue::Set(None),
241                host_user_id: ActiveValue::Set(None),
242                host_connection_id: ActiveValue::set(Some(connection.id as i32)),
243                host_connection_server_id: ActiveValue::set(Some(ServerId(
244                    connection.owner_id as i32,
245                ))),
246                id: ActiveValue::NotSet,
247                hosted_project_id: ActiveValue::Set(None),
248                dev_server_project_id: ActiveValue::Set(Some(dev_server_project_id)),
249            }
250            .insert(&*tx)
251            .await?;
252
253            if !worktrees.is_empty() {
254                worktree::Entity::insert_many(worktrees.iter().map(|worktree| {
255                    worktree::ActiveModel {
256                        id: ActiveValue::set(worktree.id as i64),
257                        project_id: ActiveValue::set(project.id),
258                        abs_path: ActiveValue::set(worktree.abs_path.clone()),
259                        root_name: ActiveValue::set(worktree.root_name.clone()),
260                        visible: ActiveValue::set(worktree.visible),
261                        scan_id: ActiveValue::set(0),
262                        completed_scan_id: ActiveValue::set(0),
263                    }
264                }))
265                .exec(&*tx)
266                .await?;
267            }
268
269            let status = self
270                .dev_server_projects_update_internal(dev_server.user_id, &tx)
271                .await?;
272
273            Ok((
274                dev_server_project.to_proto(Some(project)),
275                dev_server.user_id,
276                status,
277            ))
278        })
279        .await
280    }
281
282    pub async fn reshare_dev_server_projects(
283        &self,
284        reshared_projects: &Vec<proto::UpdateProject>,
285        dev_server_id: DevServerId,
286        connection: ConnectionId,
287    ) -> crate::Result<Vec<ResharedProject>> {
288        self.transaction(|tx| async move {
289            let mut ret = Vec::new();
290            for reshared_project in reshared_projects {
291                let project_id = ProjectId::from_proto(reshared_project.project_id);
292                let (project, dev_server_project) = project::Entity::find_by_id(project_id)
293                    .find_also_related(dev_server_project::Entity)
294                    .one(&*tx)
295                    .await?
296                    .ok_or_else(|| anyhow!("project does not exist"))?;
297
298                if dev_server_project.map(|rp| rp.dev_server_id) != Some(dev_server_id) {
299                    return Err(anyhow!("dev server project reshared from wrong server"))?;
300                }
301
302                let Ok(old_connection_id) = project.host_connection() else {
303                    return Err(anyhow!("dev server project was not shared"))?;
304                };
305
306                project::Entity::update(project::ActiveModel {
307                    id: ActiveValue::set(project_id),
308                    host_connection_id: ActiveValue::set(Some(connection.id as i32)),
309                    host_connection_server_id: ActiveValue::set(Some(ServerId(
310                        connection.owner_id as i32,
311                    ))),
312                    ..Default::default()
313                })
314                .exec(&*tx)
315                .await?;
316
317                let collaborators = project
318                    .find_related(project_collaborator::Entity)
319                    .all(&*tx)
320                    .await?;
321
322                self.update_project_worktrees(project_id, &reshared_project.worktrees, &tx)
323                    .await?;
324
325                ret.push(super::ResharedProject {
326                    id: project_id,
327                    old_connection_id,
328                    collaborators: collaborators
329                        .iter()
330                        .map(|collaborator| super::ProjectCollaborator {
331                            connection_id: collaborator.connection(),
332                            user_id: collaborator.user_id,
333                            replica_id: collaborator.replica_id,
334                            is_host: collaborator.is_host,
335                        })
336                        .collect(),
337                    worktrees: reshared_project.worktrees.clone(),
338                });
339            }
340            Ok(ret)
341        })
342        .await
343    }
344
345    pub async fn rejoin_dev_server_projects(
346        &self,
347        rejoined_projects: &Vec<proto::RejoinProject>,
348        user_id: UserId,
349        connection_id: ConnectionId,
350    ) -> crate::Result<Vec<RejoinedProject>> {
351        self.transaction(|tx| async move {
352            let mut ret = Vec::new();
353            for rejoined_project in rejoined_projects {
354                if let Some(project) = self
355                    .rejoin_project_internal(&tx, rejoined_project, user_id, connection_id)
356                    .await?
357                {
358                    ret.push(project);
359                }
360            }
361            Ok(ret)
362        })
363        .await
364    }
365}