use anyhow::anyhow; use rpc::{ proto::{self}, ConnectionId, }; use sea_orm::{ ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseTransaction, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter, }; use crate::db::ProjectId; use super::{ dev_server, dev_server_project, project, project_collaborator, worktree, Database, DevServerId, DevServerProjectId, RejoinedProject, ResharedProject, ServerId, UserId, }; impl Database { pub async fn get_dev_server_project( &self, dev_server_project_id: DevServerProjectId, ) -> crate::Result { self.transaction(|tx| async move { Ok( dev_server_project::Entity::find_by_id(dev_server_project_id) .one(&*tx) .await? .ok_or_else(|| { anyhow!("no dev server project with id {}", dev_server_project_id) })?, ) }) .await } pub async fn get_projects_for_dev_server( &self, dev_server_id: DevServerId, ) -> crate::Result> { self.transaction(|tx| async move { self.get_projects_for_dev_server_internal(dev_server_id, &tx) .await }) .await } pub async fn get_projects_for_dev_server_internal( &self, dev_server_id: DevServerId, tx: &DatabaseTransaction, ) -> crate::Result> { let servers = dev_server_project::Entity::find() .filter(dev_server_project::Column::DevServerId.eq(dev_server_id)) .find_also_related(project::Entity) .all(tx) .await?; Ok(servers .into_iter() .map(|(dev_server_project, project)| dev_server_project.to_proto(project)) .collect()) } pub async fn dev_server_project_ids_for_user( &self, user_id: UserId, tx: &DatabaseTransaction, ) -> crate::Result> { let dev_servers = dev_server::Entity::find() .filter(dev_server::Column::UserId.eq(user_id)) .find_with_related(dev_server_project::Entity) .all(tx) .await?; Ok(dev_servers .into_iter() .flat_map(|(_, projects)| projects.into_iter().map(|p| p.id)) .collect()) } pub async fn owner_for_dev_server_project( &self, dev_server_project_id: DevServerProjectId, tx: &DatabaseTransaction, ) -> crate::Result { let dev_server = dev_server_project::Entity::find_by_id(dev_server_project_id) .find_also_related(dev_server::Entity) .one(tx) .await? .and_then(|(_, dev_server)| dev_server) .ok_or_else(|| anyhow!("no dev server project"))?; Ok(dev_server.user_id) } pub async fn get_stale_dev_server_projects( &self, connection: ConnectionId, ) -> crate::Result> { self.transaction(|tx| async move { let projects = project::Entity::find() .filter( Condition::all() .add(project::Column::HostConnectionId.eq(connection.id)) .add(project::Column::HostConnectionServerId.eq(connection.owner_id)), ) .all(&*tx) .await?; Ok(projects.into_iter().map(|p| p.id).collect()) }) .await } pub async fn create_dev_server_project( &self, dev_server_id: DevServerId, path: &str, user_id: UserId, ) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> { self.transaction(|tx| async move { let dev_server = dev_server::Entity::find_by_id(dev_server_id) .one(&*tx) .await? .ok_or_else(|| anyhow!("no dev server with id {}", dev_server_id))?; if dev_server.user_id != user_id { return Err(anyhow!("not your dev server"))?; } let project = dev_server_project::Entity::insert(dev_server_project::ActiveModel { id: ActiveValue::NotSet, dev_server_id: ActiveValue::Set(dev_server_id), paths: ActiveValue::Set(dev_server_project::JSONPaths(vec![path.to_string()])), }) .exec_with_returning(&*tx) .await?; let status = self .dev_server_projects_update_internal(user_id, &tx) .await?; Ok((project, status)) }) .await } pub async fn update_dev_server_project( &self, id: DevServerProjectId, paths: &[String], user_id: UserId, ) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> { self.transaction(move |tx| async move { let paths = paths.to_owned(); let Some((project, Some(dev_server))) = dev_server_project::Entity::find_by_id(id) .find_also_related(dev_server::Entity) .one(&*tx) .await? else { return Err(anyhow!("no such dev server project"))?; }; if dev_server.user_id != user_id { return Err(anyhow!("not your dev server"))?; } let mut project = project.into_active_model(); project.paths = ActiveValue::Set(dev_server_project::JSONPaths(paths)); let project = project.update(&*tx).await?; let status = self .dev_server_projects_update_internal(user_id, &tx) .await?; Ok((project, status)) }) .await } pub async fn delete_dev_server_project( &self, dev_server_project_id: DevServerProjectId, dev_server_id: DevServerId, user_id: UserId, ) -> crate::Result<(Vec, proto::DevServerProjectsUpdate)> { self.transaction(|tx| async move { project::Entity::delete_many() .filter(project::Column::DevServerProjectId.eq(dev_server_project_id)) .exec(&*tx) .await?; let result = dev_server_project::Entity::delete_by_id(dev_server_project_id) .exec(&*tx) .await?; if result.rows_affected != 1 { return Err(anyhow!( "no dev server project with id {}", dev_server_project_id ))?; } let status = self .dev_server_projects_update_internal(user_id, &tx) .await?; let projects = self .get_projects_for_dev_server_internal(dev_server_id, &tx) .await?; Ok((projects, status)) }) .await } pub async fn share_dev_server_project( &self, dev_server_project_id: DevServerProjectId, dev_server_id: DevServerId, connection: ConnectionId, worktrees: &[proto::WorktreeMetadata], ) -> crate::Result<( proto::DevServerProject, UserId, proto::DevServerProjectsUpdate, )> { self.transaction(|tx| async move { let dev_server = dev_server::Entity::find_by_id(dev_server_id) .one(&*tx) .await? .ok_or_else(|| anyhow!("no dev server with id {}", dev_server_id))?; let dev_server_project = dev_server_project::Entity::find_by_id(dev_server_project_id) .one(&*tx) .await? .ok_or_else(|| { anyhow!("no dev server project with id {}", dev_server_project_id) })?; if dev_server_project.dev_server_id != dev_server_id { return Err(anyhow!("dev server project shared from wrong server"))?; } let project = project::ActiveModel { room_id: ActiveValue::Set(None), host_user_id: ActiveValue::Set(None), host_connection_id: ActiveValue::set(Some(connection.id as i32)), host_connection_server_id: ActiveValue::set(Some(ServerId( connection.owner_id as i32, ))), id: ActiveValue::NotSet, hosted_project_id: ActiveValue::Set(None), dev_server_project_id: ActiveValue::Set(Some(dev_server_project_id)), } .insert(&*tx) .await?; if !worktrees.is_empty() { worktree::Entity::insert_many(worktrees.iter().map(|worktree| { worktree::ActiveModel { id: ActiveValue::set(worktree.id as i64), project_id: ActiveValue::set(project.id), abs_path: ActiveValue::set(worktree.abs_path.clone()), root_name: ActiveValue::set(worktree.root_name.clone()), visible: ActiveValue::set(worktree.visible), scan_id: ActiveValue::set(0), completed_scan_id: ActiveValue::set(0), } })) .exec(&*tx) .await?; } let status = self .dev_server_projects_update_internal(dev_server.user_id, &tx) .await?; Ok(( dev_server_project.to_proto(Some(project)), dev_server.user_id, status, )) }) .await } pub async fn reshare_dev_server_projects( &self, reshared_projects: &Vec, dev_server_id: DevServerId, connection: ConnectionId, ) -> crate::Result> { self.transaction(|tx| async move { let mut ret = Vec::new(); for reshared_project in reshared_projects { let project_id = ProjectId::from_proto(reshared_project.project_id); let (project, dev_server_project) = project::Entity::find_by_id(project_id) .find_also_related(dev_server_project::Entity) .one(&*tx) .await? .ok_or_else(|| anyhow!("project does not exist"))?; if dev_server_project.map(|rp| rp.dev_server_id) != Some(dev_server_id) { return Err(anyhow!("dev server project reshared from wrong server"))?; } let Ok(old_connection_id) = project.host_connection() else { return Err(anyhow!("dev server project was not shared"))?; }; project::Entity::update(project::ActiveModel { id: ActiveValue::set(project_id), host_connection_id: ActiveValue::set(Some(connection.id as i32)), host_connection_server_id: ActiveValue::set(Some(ServerId( connection.owner_id as i32, ))), ..Default::default() }) .exec(&*tx) .await?; let collaborators = project .find_related(project_collaborator::Entity) .all(&*tx) .await?; self.update_project_worktrees(project_id, &reshared_project.worktrees, &tx) .await?; ret.push(super::ResharedProject { id: project_id, old_connection_id, collaborators: collaborators .iter() .map(|collaborator| super::ProjectCollaborator { connection_id: collaborator.connection(), user_id: collaborator.user_id, replica_id: collaborator.replica_id, is_host: collaborator.is_host, }) .collect(), worktrees: reshared_project.worktrees.clone(), }); } Ok(ret) }) .await } pub async fn rejoin_dev_server_projects( &self, rejoined_projects: &Vec, user_id: UserId, connection_id: ConnectionId, ) -> crate::Result> { self.transaction(|tx| async move { let mut ret = Vec::new(); for rejoined_project in rejoined_projects { if let Some(project) = self .rejoin_project_internal(&tx, rejoined_project, user_id, connection_id) .await? { ret.push(project); } } Ok(ret) }) .await } }