dev_servers.rs

  1use rpc::proto;
  2use sea_orm::{
  3    ActiveValue, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel, QueryFilter,
  4};
  5
  6use super::{dev_server, dev_server_project, Database, DevServerId, UserId};
  7
  8impl Database {
  9    pub async fn get_dev_server(
 10        &self,
 11        dev_server_id: DevServerId,
 12    ) -> crate::Result<dev_server::Model> {
 13        self.transaction(|tx| async move {
 14            Ok(dev_server::Entity::find_by_id(dev_server_id)
 15                .one(&*tx)
 16                .await?
 17                .ok_or_else(|| anyhow::anyhow!("no dev server with id {}", dev_server_id))?)
 18        })
 19        .await
 20    }
 21
 22    pub async fn get_dev_servers(&self, user_id: UserId) -> crate::Result<Vec<dev_server::Model>> {
 23        self.transaction(|tx| async move {
 24            Ok(dev_server::Entity::find()
 25                .filter(dev_server::Column::UserId.eq(user_id))
 26                .all(&*tx)
 27                .await?)
 28        })
 29        .await
 30    }
 31
 32    pub async fn dev_server_projects_update(
 33        &self,
 34        user_id: UserId,
 35    ) -> crate::Result<proto::DevServerProjectsUpdate> {
 36        self.transaction(|tx| async move {
 37            self.dev_server_projects_update_internal(user_id, &tx).await
 38        })
 39        .await
 40    }
 41
 42    pub async fn dev_server_projects_update_internal(
 43        &self,
 44        user_id: UserId,
 45        tx: &DatabaseTransaction,
 46    ) -> crate::Result<proto::DevServerProjectsUpdate> {
 47        let dev_servers = dev_server::Entity::find()
 48            .filter(dev_server::Column::UserId.eq(user_id))
 49            .all(tx)
 50            .await?;
 51
 52        let dev_server_projects = dev_server_project::Entity::find()
 53            .filter(
 54                dev_server_project::Column::DevServerId
 55                    .is_in(dev_servers.iter().map(|d| d.id).collect::<Vec<_>>()),
 56            )
 57            .find_also_related(super::project::Entity)
 58            .all(tx)
 59            .await?;
 60
 61        Ok(proto::DevServerProjectsUpdate {
 62            dev_servers: dev_servers
 63                .into_iter()
 64                .map(|d| d.to_proto(proto::DevServerStatus::Offline))
 65                .collect(),
 66            dev_server_projects: dev_server_projects
 67                .into_iter()
 68                .map(|(dev_server_project, project)| dev_server_project.to_proto(project))
 69                .collect(),
 70        })
 71    }
 72
 73    pub async fn create_dev_server(
 74        &self,
 75        name: &str,
 76        ssh_connection_string: Option<&str>,
 77        hashed_access_token: &str,
 78        user_id: UserId,
 79    ) -> crate::Result<(dev_server::Model, proto::DevServerProjectsUpdate)> {
 80        self.transaction(|tx| async move {
 81            if name.trim().is_empty() {
 82                return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
 83            }
 84
 85            let dev_server = dev_server::Entity::insert(dev_server::ActiveModel {
 86                id: ActiveValue::NotSet,
 87                hashed_token: ActiveValue::Set(hashed_access_token.to_string()),
 88                name: ActiveValue::Set(name.trim().to_string()),
 89                user_id: ActiveValue::Set(user_id),
 90                ssh_connection_string: ActiveValue::Set(
 91                    ssh_connection_string.map(ToOwned::to_owned),
 92                ),
 93            })
 94            .exec_with_returning(&*tx)
 95            .await?;
 96
 97            let dev_server_projects = self
 98                .dev_server_projects_update_internal(user_id, &tx)
 99                .await?;
100
101            Ok((dev_server, dev_server_projects))
102        })
103        .await
104    }
105
106    pub async fn update_dev_server_token(
107        &self,
108        id: DevServerId,
109        hashed_token: &str,
110        user_id: UserId,
111    ) -> crate::Result<proto::DevServerProjectsUpdate> {
112        self.transaction(|tx| async move {
113            let Some(dev_server) = dev_server::Entity::find_by_id(id).one(&*tx).await? else {
114                return Err(anyhow::anyhow!("no dev server with id {}", id))?;
115            };
116            if dev_server.user_id != user_id {
117                return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
118            }
119
120            dev_server::Entity::update(dev_server::ActiveModel {
121                hashed_token: ActiveValue::Set(hashed_token.to_string()),
122                ..dev_server.clone().into_active_model()
123            })
124            .exec(&*tx)
125            .await?;
126
127            let dev_server_projects = self
128                .dev_server_projects_update_internal(user_id, &tx)
129                .await?;
130
131            Ok(dev_server_projects)
132        })
133        .await
134    }
135
136    pub async fn rename_dev_server(
137        &self,
138        id: DevServerId,
139        name: &str,
140        ssh_connection_string: Option<&str>,
141        user_id: UserId,
142    ) -> crate::Result<proto::DevServerProjectsUpdate> {
143        self.transaction(|tx| async move {
144            let Some(dev_server) = dev_server::Entity::find_by_id(id).one(&*tx).await? else {
145                return Err(anyhow::anyhow!("no dev server with id {}", id))?;
146            };
147            if dev_server.user_id != user_id || name.trim().is_empty() {
148                return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
149            }
150
151            dev_server::Entity::update(dev_server::ActiveModel {
152                name: ActiveValue::Set(name.trim().to_string()),
153                ssh_connection_string: ActiveValue::Set(
154                    ssh_connection_string.map(ToOwned::to_owned),
155                ),
156                ..dev_server.clone().into_active_model()
157            })
158            .exec(&*tx)
159            .await?;
160
161            let dev_server_projects = self
162                .dev_server_projects_update_internal(user_id, &tx)
163                .await?;
164
165            Ok(dev_server_projects)
166        })
167        .await
168    }
169
170    pub async fn delete_dev_server(
171        &self,
172        id: DevServerId,
173        user_id: UserId,
174    ) -> crate::Result<proto::DevServerProjectsUpdate> {
175        self.transaction(|tx| async move {
176            let Some(dev_server) = dev_server::Entity::find_by_id(id).one(&*tx).await? else {
177                return Err(anyhow::anyhow!("no dev server with id {}", id))?;
178            };
179            if dev_server.user_id != user_id {
180                return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
181            }
182
183            dev_server_project::Entity::delete_many()
184                .filter(dev_server_project::Column::DevServerId.eq(id))
185                .exec(&*tx)
186                .await?;
187
188            dev_server::Entity::delete(dev_server.into_active_model())
189                .exec(&*tx)
190                .await?;
191
192            let dev_server_projects = self
193                .dev_server_projects_update_internal(user_id, &tx)
194                .await?;
195
196            Ok(dev_server_projects)
197        })
198        .await
199    }
200}