1use crate::{
2 models::{InsertQueryBuilder, UpdateQueryBuilder},
3 prelude::*,
4};
5use garde::Validate;
6use serde::{Deserialize, Serialize};
7use sqlx::{Row, postgres::PgRow};
8use std::{
9 collections::BTreeMap,
10 sync::{Arc, LazyLock},
11};
12use utoipa::ToSchema;
13
14#[derive(Serialize, Deserialize, Clone)]
15pub struct Mount {
16 pub uuid: uuid::Uuid,
17
18 pub name: compact_str::CompactString,
19 pub description: Option<compact_str::CompactString>,
20
21 pub source: compact_str::CompactString,
22 pub target: compact_str::CompactString,
23
24 pub read_only: bool,
25 pub user_mountable: bool,
26
27 pub created: chrono::NaiveDateTime,
28}
29
30impl BaseModel for Mount {
31 const NAME: &'static str = "mount";
32
33 #[inline]
34 fn columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
35 let prefix = prefix.unwrap_or_default();
36
37 BTreeMap::from([
38 ("mounts.uuid", compact_str::format_compact!("{prefix}uuid")),
39 ("mounts.name", compact_str::format_compact!("{prefix}name")),
40 (
41 "mounts.description",
42 compact_str::format_compact!("{prefix}description"),
43 ),
44 (
45 "mounts.source",
46 compact_str::format_compact!("{prefix}source"),
47 ),
48 (
49 "mounts.target",
50 compact_str::format_compact!("{prefix}target"),
51 ),
52 (
53 "mounts.read_only",
54 compact_str::format_compact!("{prefix}read_only"),
55 ),
56 (
57 "mounts.user_mountable",
58 compact_str::format_compact!("{prefix}user_mountable"),
59 ),
60 (
61 "mounts.created",
62 compact_str::format_compact!("{prefix}created"),
63 ),
64 ])
65 }
66
67 #[inline]
68 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
69 let prefix = prefix.unwrap_or_default();
70
71 Ok(Self {
72 uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
73 name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
74 description: row
75 .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
76 source: row.try_get(compact_str::format_compact!("{prefix}source").as_str())?,
77 target: row.try_get(compact_str::format_compact!("{prefix}target").as_str())?,
78 read_only: row.try_get(compact_str::format_compact!("{prefix}read_only").as_str())?,
79 user_mountable: row
80 .try_get(compact_str::format_compact!("{prefix}user_mountable").as_str())?,
81 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
82 })
83 }
84}
85
86impl Mount {
87 pub async fn by_node_uuid_egg_uuid_uuid(
88 database: &crate::database::Database,
89 node_uuid: uuid::Uuid,
90 egg_uuid: uuid::Uuid,
91 uuid: uuid::Uuid,
92 ) -> Result<Option<Self>, crate::database::DatabaseError> {
93 let row = sqlx::query(&format!(
94 r#"
95 SELECT {}
96 FROM mounts
97 JOIN node_mounts ON mounts.uuid = node_mounts.mount_uuid
98 JOIN nest_egg_mounts ON mounts.uuid = nest_egg_mounts.mount_uuid
99 WHERE node_mounts.node_uuid = $1 AND nest_egg_mounts.egg_uuid = $2 AND mounts.uuid = $3
100 "#,
101 Self::columns_sql(None)
102 ))
103 .bind(node_uuid)
104 .bind(egg_uuid)
105 .bind(uuid)
106 .fetch_optional(database.read())
107 .await?;
108
109 row.try_map(|row| Self::map(None, &row))
110 }
111
112 pub async fn all_with_pagination(
113 database: &crate::database::Database,
114 page: i64,
115 per_page: i64,
116 search: Option<&str>,
117 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
118 let offset = (page - 1) * per_page;
119
120 let rows = sqlx::query(&format!(
121 r#"
122 SELECT {}, COUNT(*) OVER() AS total_count
123 FROM mounts
124 WHERE ($1 IS NULL OR mounts.name ILIKE '%' || $1 || '%')
125 ORDER BY mounts.created
126 LIMIT $2 OFFSET $3
127 "#,
128 Self::columns_sql(None)
129 ))
130 .bind(search)
131 .bind(per_page)
132 .bind(offset)
133 .fetch_all(database.read())
134 .await?;
135
136 Ok(super::Pagination {
137 total: rows
138 .first()
139 .map_or(Ok(0), |row| row.try_get("total_count"))?,
140 per_page,
141 page,
142 data: rows
143 .into_iter()
144 .map(|row| Self::map(None, &row))
145 .try_collect_vec()?,
146 })
147 }
148
149 #[inline]
150 pub fn into_admin_api_object(self) -> AdminApiMount {
151 AdminApiMount {
152 uuid: self.uuid,
153 name: self.name,
154 description: self.description,
155 source: self.source,
156 target: self.target,
157 read_only: self.read_only,
158 user_mountable: self.user_mountable,
159 created: self.created.and_utc(),
160 }
161 }
162}
163
164#[async_trait::async_trait]
165impl ByUuid for Mount {
166 async fn by_uuid(
167 database: &crate::database::Database,
168 uuid: uuid::Uuid,
169 ) -> Result<Self, crate::database::DatabaseError> {
170 let row = sqlx::query(&format!(
171 r#"
172 SELECT {}
173 FROM mounts
174 WHERE mounts.uuid = $1
175 "#,
176 Self::columns_sql(None)
177 ))
178 .bind(uuid)
179 .fetch_one(database.read())
180 .await?;
181
182 Self::map(None, &row)
183 }
184}
185
186#[derive(ToSchema, Deserialize, Validate)]
187pub struct CreateMountOptions {
188 #[garde(length(chars, min = 3, max = 255))]
189 #[schema(min_length = 3, max_length = 255)]
190 pub name: compact_str::CompactString,
191 #[garde(length(chars, min = 1, max = 1024))]
192 #[schema(min_length = 1, max_length = 1024)]
193 pub description: Option<compact_str::CompactString>,
194 #[garde(length(chars, min = 1, max = 255))]
195 #[schema(min_length = 1, max_length = 255)]
196 pub source: compact_str::CompactString,
197 #[garde(length(chars, min = 1, max = 255))]
198 #[schema(min_length = 1, max_length = 255)]
199 pub target: compact_str::CompactString,
200 #[garde(skip)]
201 pub read_only: bool,
202 #[garde(skip)]
203 pub user_mountable: bool,
204}
205
206#[async_trait::async_trait]
207impl CreatableModel for Mount {
208 type CreateOptions<'a> = CreateMountOptions;
209 type CreateResult = Self;
210
211 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
212 static CREATE_LISTENERS: LazyLock<CreateListenerList<Mount>> =
213 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
214
215 &CREATE_LISTENERS
216 }
217
218 async fn create(
219 state: &crate::State,
220 mut options: Self::CreateOptions<'_>,
221 ) -> Result<Self, crate::database::DatabaseError> {
222 options.validate()?;
223
224 let mut transaction = state.database.write().begin().await?;
225
226 let mut query_builder = InsertQueryBuilder::new("mounts");
227
228 Self::run_create_handlers(&mut options, &mut query_builder, state, &mut transaction)
229 .await?;
230
231 query_builder
232 .set("name", &options.name)
233 .set("description", &options.description)
234 .set("source", &options.source)
235 .set("target", &options.target)
236 .set("read_only", options.read_only)
237 .set("user_mountable", options.user_mountable);
238
239 let row = query_builder
240 .returning(&Self::columns_sql(None))
241 .fetch_one(&mut *transaction)
242 .await?;
243 let mount = Self::map(None, &row)?;
244
245 transaction.commit().await?;
246
247 Ok(mount)
248 }
249}
250
251#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
252pub struct UpdateMountOptions {
253 #[garde(length(chars, min = 3, max = 255))]
254 #[schema(min_length = 3, max_length = 255)]
255 pub name: Option<compact_str::CompactString>,
256 #[garde(length(chars, min = 1, max = 1024))]
257 #[schema(min_length = 1, max_length = 1024)]
258 #[serde(
259 default,
260 skip_serializing_if = "Option::is_none",
261 with = "::serde_with::rust::double_option"
262 )]
263 pub description: Option<Option<compact_str::CompactString>>,
264 #[garde(length(chars, min = 1, max = 255))]
265 #[schema(min_length = 1, max_length = 255)]
266 pub source: Option<compact_str::CompactString>,
267 #[garde(length(chars, min = 1, max = 255))]
268 #[schema(min_length = 1, max_length = 255)]
269 pub target: Option<compact_str::CompactString>,
270 #[garde(skip)]
271 pub read_only: Option<bool>,
272 #[garde(skip)]
273 pub user_mountable: Option<bool>,
274}
275
276#[async_trait::async_trait]
277impl UpdatableModel for Mount {
278 type UpdateOptions = UpdateMountOptions;
279
280 fn get_update_handlers() -> &'static LazyLock<UpdateListenerList<Self>> {
281 static UPDATE_LISTENERS: LazyLock<UpdateListenerList<Mount>> =
282 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
283
284 &UPDATE_LISTENERS
285 }
286
287 async fn update(
288 &mut self,
289 state: &crate::State,
290 mut options: Self::UpdateOptions,
291 ) -> Result<(), crate::database::DatabaseError> {
292 options.validate()?;
293
294 let mut transaction = state.database.write().begin().await?;
295
296 let mut query_builder = UpdateQueryBuilder::new("mounts");
297
298 Self::run_update_handlers(
299 self,
300 &mut options,
301 &mut query_builder,
302 state,
303 &mut transaction,
304 )
305 .await?;
306
307 query_builder
308 .set("name", options.name.as_ref())
309 .set(
310 "description",
311 options.description.as_ref().map(|d| d.as_ref()),
312 )
313 .set("source", options.source.as_ref())
314 .set("target", options.target.as_ref())
315 .set("read_only", options.read_only)
316 .set("user_mountable", options.user_mountable)
317 .where_eq("uuid", self.uuid);
318
319 query_builder.execute(&mut *transaction).await?;
320
321 if let Some(name) = options.name {
322 self.name = name;
323 }
324 if let Some(description) = options.description {
325 self.description = description;
326 }
327 if let Some(source) = options.source {
328 self.source = source;
329 }
330 if let Some(target) = options.target {
331 self.target = target;
332 }
333 if let Some(read_only) = options.read_only {
334 self.read_only = read_only;
335 }
336 if let Some(user_mountable) = options.user_mountable {
337 self.user_mountable = user_mountable;
338 }
339
340 transaction.commit().await?;
341
342 Ok(())
343 }
344}
345
346#[async_trait::async_trait]
347impl DeletableModel for Mount {
348 type DeleteOptions = ();
349
350 fn get_delete_handlers() -> &'static LazyLock<DeleteListenerList<Self>> {
351 static DELETE_LISTENERS: LazyLock<DeleteListenerList<Mount>> =
352 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
353
354 &DELETE_LISTENERS
355 }
356
357 async fn delete(
358 &self,
359 state: &crate::State,
360 options: Self::DeleteOptions,
361 ) -> Result<(), anyhow::Error> {
362 let mut transaction = state.database.write().begin().await?;
363
364 self.run_delete_handlers(&options, state, &mut transaction)
365 .await?;
366
367 sqlx::query(
368 r#"
369 DELETE FROM mounts
370 WHERE mounts.uuid = $1
371 "#,
372 )
373 .bind(self.uuid)
374 .execute(&mut *transaction)
375 .await?;
376
377 transaction.commit().await?;
378
379 Ok(())
380 }
381}
382
383#[derive(ToSchema, Serialize)]
384#[schema(title = "Mount")]
385pub struct AdminApiMount {
386 pub uuid: uuid::Uuid,
387
388 pub name: compact_str::CompactString,
389 pub description: Option<compact_str::CompactString>,
390
391 pub source: compact_str::CompactString,
392 pub target: compact_str::CompactString,
393
394 pub read_only: bool,
395 pub user_mountable: bool,
396
397 pub created: chrono::DateTime<chrono::Utc>,
398}