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