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 extension_data: super::ModelExtensionData,
30}
31
32impl BaseModel for Mount {
33 const NAME: &'static str = "mount";
34
35 fn get_extension_list() -> &'static super::ModelExtensionList {
36 static EXTENSIONS: LazyLock<super::ModelExtensionList> =
37 LazyLock::new(|| parking_lot::RwLock::new(Vec::new()));
38
39 &EXTENSIONS
40 }
41
42 fn get_extension_data(&self) -> &super::ModelExtensionData {
43 &self.extension_data
44 }
45
46 #[inline]
47 fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
48 let prefix = prefix.unwrap_or_default();
49
50 BTreeMap::from([
51 ("mounts.uuid", compact_str::format_compact!("{prefix}uuid")),
52 ("mounts.name", compact_str::format_compact!("{prefix}name")),
53 (
54 "mounts.description",
55 compact_str::format_compact!("{prefix}description"),
56 ),
57 (
58 "mounts.source",
59 compact_str::format_compact!("{prefix}source"),
60 ),
61 (
62 "mounts.target",
63 compact_str::format_compact!("{prefix}target"),
64 ),
65 (
66 "mounts.read_only",
67 compact_str::format_compact!("{prefix}read_only"),
68 ),
69 (
70 "mounts.user_mountable",
71 compact_str::format_compact!("{prefix}user_mountable"),
72 ),
73 (
74 "mounts.created",
75 compact_str::format_compact!("{prefix}created"),
76 ),
77 ])
78 }
79
80 #[inline]
81 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
82 let prefix = prefix.unwrap_or_default();
83
84 Ok(Self {
85 uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
86 name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
87 description: row
88 .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
89 source: row.try_get(compact_str::format_compact!("{prefix}source").as_str())?,
90 target: row.try_get(compact_str::format_compact!("{prefix}target").as_str())?,
91 read_only: row.try_get(compact_str::format_compact!("{prefix}read_only").as_str())?,
92 user_mountable: row
93 .try_get(compact_str::format_compact!("{prefix}user_mountable").as_str())?,
94 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
95 extension_data: Self::map_extensions(prefix, row)?,
96 })
97 }
98
99 fn cache_invalidation_keys(&self) -> Vec<compact_str::CompactString> {
100 vec![compact_str::format_compact!(
101 "{}::{}",
102 Self::NAME,
103 self.uuid
104 )]
105 }
106}
107
108impl Mount {
109 pub async fn by_node_uuid_egg_uuid_uuid(
110 database: &crate::database::Database,
111 node_uuid: uuid::Uuid,
112 egg_uuid: uuid::Uuid,
113 uuid: uuid::Uuid,
114 ) -> Result<Option<Self>, crate::database::DatabaseError> {
115 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
116 r#"
117 SELECT {}
118 FROM mounts
119 JOIN node_mounts ON mounts.uuid = node_mounts.mount_uuid
120 JOIN nest_egg_mounts ON mounts.uuid = nest_egg_mounts.mount_uuid
121 WHERE node_mounts.node_uuid = $1 AND nest_egg_mounts.egg_uuid = $2 AND mounts.uuid = $3
122 "#,
123 Self::columns_sql(None)
124 )))
125 .bind(node_uuid)
126 .bind(egg_uuid)
127 .bind(uuid)
128 .fetch_optional(database.read())
129 .await?;
130
131 row.try_map(|row| Self::map(None, &row))
132 }
133
134 pub async fn all_with_pagination(
135 database: &crate::database::Database,
136 page: i64,
137 per_page: i64,
138 search: Option<&str>,
139 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
140 super::PaginatedQueryBuilder::new(
141 Self::columns_sql(None),
142 format!(
143 r#"
144 FROM mounts
145 WHERE {search}
146 "#,
147 search = super::search_sql(1, &["mounts.name"], &["mounts.uuid"]),
148 ),
149 "mounts.created, mounts.uuid",
150 )
151 .bind(search)
152 .fetch(database, page, per_page)
153 .await
154 }
155}
156
157#[async_trait::async_trait]
158impl IntoAdminApiObject for Mount {
159 type AdminApiObject = AdminApiMount;
160 type ExtraArgs<'a> = ();
161
162 async fn into_admin_api_object<'a>(
163 self,
164 state: &crate::State,
165 _args: Self::ExtraArgs<'a>,
166 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
167 let api_object = AdminApiMount::init_hooks(&self, state).await?;
168
169 let api_object = finish_extendible!(
170 AdminApiMount {
171 uuid: self.uuid,
172 name: self.name,
173 description: self.description,
174 source: self.source,
175 target: self.target,
176 read_only: self.read_only,
177 user_mountable: self.user_mountable,
178 created: self.created.and_utc(),
179 },
180 api_object,
181 state
182 )?;
183
184 Ok(api_object)
185 }
186}
187
188#[async_trait::async_trait]
189impl ByUuid for Mount {
190 async fn by_uuid(
191 database: &crate::database::Database,
192 uuid: uuid::Uuid,
193 ) -> Result<Self, crate::database::DatabaseError> {
194 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
195 r#"
196 SELECT {}
197 FROM mounts
198 WHERE mounts.uuid = $1
199 "#,
200 Self::columns_sql(None)
201 )))
202 .bind(uuid)
203 .fetch_one(database.read())
204 .await?;
205
206 Self::map(None, &row)
207 }
208
209 async fn by_uuid_with_transaction(
210 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
211 uuid: uuid::Uuid,
212 ) -> Result<Self, crate::database::DatabaseError> {
213 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
214 r#"
215 SELECT {}
216 FROM mounts
217 WHERE mounts.uuid = $1
218 "#,
219 Self::columns_sql(None)
220 )))
221 .bind(uuid)
222 .fetch_one(&mut **transaction)
223 .await?;
224
225 Self::map(None, &row)
226 }
227}
228
229#[derive(ToSchema, Deserialize, Validate)]
230pub struct CreateMountOptions {
231 #[garde(length(chars, min = 1, max = 255))]
232 #[schema(min_length = 1, max_length = 255)]
233 pub name: compact_str::CompactString,
234 #[garde(length(chars, min = 1, max = 1024))]
235 #[schema(min_length = 1, max_length = 1024)]
236 pub description: Option<compact_str::CompactString>,
237 #[garde(length(chars, min = 1, max = 255))]
238 #[schema(min_length = 1, max_length = 255)]
239 pub source: compact_str::CompactString,
240 #[garde(length(chars, min = 1, max = 255))]
241 #[schema(min_length = 1, max_length = 255)]
242 pub target: compact_str::CompactString,
243 #[garde(skip)]
244 pub read_only: bool,
245 #[garde(skip)]
246 pub user_mountable: bool,
247}
248
249#[async_trait::async_trait]
250impl CreatableModel for Mount {
251 type CreateOptions<'a> = CreateMountOptions;
252 type CreateResult = Self;
253
254 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
255 static CREATE_LISTENERS: LazyLock<CreateListenerList<Mount>> =
256 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
257
258 &CREATE_LISTENERS
259 }
260
261 async fn create_with_transaction(
262 state: &crate::State,
263 mut options: Self::CreateOptions<'_>,
264 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
265 ) -> Result<Self, crate::database::DatabaseError> {
266 options.validate()?;
267
268 let mut query_builder = InsertQueryBuilder::new("mounts");
269
270 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
271
272 query_builder
273 .set("name", &options.name)
274 .set("description", &options.description)
275 .set("source", &options.source)
276 .set("target", &options.target)
277 .set("read_only", options.read_only)
278 .set("user_mountable", options.user_mountable);
279
280 let row = query_builder
281 .returning(&Self::columns_sql(None))
282 .fetch_one(&mut **transaction)
283 .await?;
284 let mut mount = Self::map(None, &row)?;
285
286 Self::run_after_create_handlers(&mut mount, &options, state, transaction).await?;
287
288 Ok(mount)
289 }
290}
291
292#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
293pub struct UpdateMountOptions {
294 #[garde(length(chars, min = 1, max = 255))]
295 #[schema(min_length = 1, max_length = 255)]
296 pub name: Option<compact_str::CompactString>,
297 #[garde(length(chars, min = 1, max = 1024))]
298 #[schema(min_length = 1, max_length = 1024)]
299 #[serde(
300 default,
301 skip_serializing_if = "Option::is_none",
302 with = "::serde_with::rust::double_option"
303 )]
304 pub description: Option<Option<compact_str::CompactString>>,
305 #[garde(length(chars, min = 1, max = 255))]
306 #[schema(min_length = 1, max_length = 255)]
307 pub source: Option<compact_str::CompactString>,
308 #[garde(length(chars, min = 1, max = 255))]
309 #[schema(min_length = 1, max_length = 255)]
310 pub target: Option<compact_str::CompactString>,
311 #[garde(skip)]
312 pub read_only: Option<bool>,
313 #[garde(skip)]
314 pub user_mountable: Option<bool>,
315}
316
317#[async_trait::async_trait]
318impl UpdatableModel for Mount {
319 type UpdateOptions = UpdateMountOptions;
320
321 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
322 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<Mount>> =
323 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
324
325 &UPDATE_LISTENERS
326 }
327
328 async fn update_with_transaction(
329 &mut self,
330 state: &crate::State,
331 mut options: Self::UpdateOptions,
332 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
333 ) -> Result<(), crate::database::DatabaseError> {
334 options.validate()?;
335
336 let mut query_builder = UpdateQueryBuilder::new("mounts");
337
338 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
339 .await?;
340
341 query_builder
342 .set("name", options.name.as_ref())
343 .set(
344 "description",
345 options.description.as_ref().map(|d| d.as_ref()),
346 )
347 .set("source", options.source.as_ref())
348 .set("target", options.target.as_ref())
349 .set("read_only", options.read_only)
350 .set("user_mountable", options.user_mountable)
351 .where_eq("uuid", self.uuid);
352
353 query_builder.execute(&mut **transaction).await?;
354
355 if let Some(name) = options.name {
356 self.name = name;
357 }
358 if let Some(description) = options.description {
359 self.description = description;
360 }
361 if let Some(source) = options.source {
362 self.source = source;
363 }
364 if let Some(target) = options.target {
365 self.target = target;
366 }
367 if let Some(read_only) = options.read_only {
368 self.read_only = read_only;
369 }
370 if let Some(user_mountable) = options.user_mountable {
371 self.user_mountable = user_mountable;
372 }
373
374 self.run_after_update_handlers(state, transaction).await?;
375
376 Ok(())
377 }
378}
379
380#[async_trait::async_trait]
381impl DeletableModel for Mount {
382 type DeleteOptions = ();
383
384 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
385 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<Mount>> =
386 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
387
388 &DELETE_LISTENERS
389 }
390
391 async fn delete_with_transaction(
392 &self,
393 state: &crate::State,
394 options: Self::DeleteOptions,
395 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
396 ) -> Result<(), anyhow::Error> {
397 self.run_delete_handlers(&options, state, transaction)
398 .await?;
399
400 sqlx::query(
401 r#"
402 DELETE FROM mounts
403 WHERE mounts.uuid = $1
404 "#,
405 )
406 .bind(self.uuid)
407 .execute(&mut **transaction)
408 .await?;
409
410 self.run_after_delete_handlers(&options, state, transaction)
411 .await?;
412
413 Ok(())
414 }
415}
416
417#[derive(Validate)]
418pub struct DuplicateMountOptions {
419 #[garde(length(chars, min = 1, max = 255))]
420 pub name: compact_str::CompactString,
421 #[garde(length(chars, min = 1, max = 255))]
422 pub source: compact_str::CompactString,
423 #[garde(length(chars, min = 1, max = 255))]
424 pub target: compact_str::CompactString,
425}
426
427#[async_trait::async_trait]
428impl DuplicableModel for Mount {
429 type DuplicateOptions<'a> = DuplicateMountOptions;
430 type DuplicateResult = Self;
431
432 fn get_duplicate_handlers() -> &'static LazyLock<DuplicateHandlerList<Self>> {
433 static DUPLICATE_LISTENERS: LazyLock<DuplicateHandlerList<Mount>> =
434 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
435
436 &DUPLICATE_LISTENERS
437 }
438
439 async fn duplicate_with_transaction(
440 &self,
441 state: &crate::State,
442 options: Self::DuplicateOptions<'_>,
443 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
444 ) -> Result<Self, crate::database::DatabaseError> {
445 options.validate()?;
446
447 self.run_duplicate_handlers(&options, state, transaction)
448 .await?;
449
450 let mut query_builder = InsertQueryBuilder::new("mounts");
451
452 query_builder
453 .set("name", &options.name)
454 .set("description", &self.description)
455 .set("source", &options.source)
456 .set("target", &options.target)
457 .set("read_only", self.read_only)
458 .set("user_mountable", self.user_mountable);
459
460 let row = query_builder
461 .returning(&Self::columns_sql(None))
462 .fetch_one(&mut **transaction)
463 .await?;
464 let mut mount = Self::map(None, &row)?;
465
466 self.run_after_duplicate_handlers(&mut mount, &options, state, transaction)
467 .await?;
468
469 Ok(mount)
470 }
471}
472
473#[schema_extension_derive::extendible]
474#[init_args(Mount, crate::State)]
475#[hook_args(crate::State)]
476#[derive(ToSchema, Serialize)]
477#[schema(title = "Mount")]
478pub struct AdminApiMount {
479 pub uuid: uuid::Uuid,
480
481 pub name: compact_str::CompactString,
482 pub description: Option<compact_str::CompactString>,
483
484 pub source: compact_str::CompactString,
485 pub target: compact_str::CompactString,
486
487 pub read_only: bool,
488 pub user_mountable: bool,
489
490 pub created: chrono::DateTime<chrono::Utc>,
491}