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}