365 lines
13 KiB
Rust
365 lines
13 KiB
Rust
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<dev_server_project::Model> {
|
|
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<Vec<proto::DevServerProject>> {
|
|
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<Vec<proto::DevServerProject>> {
|
|
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<Vec<DevServerProjectId>> {
|
|
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<UserId> {
|
|
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<Vec<ProjectId>> {
|
|
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::DevServerProject>, 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<proto::UpdateProject>,
|
|
dev_server_id: DevServerId,
|
|
connection: ConnectionId,
|
|
) -> crate::Result<Vec<ResharedProject>> {
|
|
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<proto::RejoinProject>,
|
|
user_id: UserId,
|
|
connection_id: ConnectionId,
|
|
) -> crate::Result<Vec<RejoinedProject>> {
|
|
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
|
|
}
|
|
}
|