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(|| std::sync::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
100impl Mount {
101 pub async fn by_node_uuid_egg_uuid_uuid(
102 database: &crate::database::Database,
103 node_uuid: uuid::Uuid,
104 egg_uuid: uuid::Uuid,
105 uuid: uuid::Uuid,
106 ) -> Result<Option<Self>, crate::database::DatabaseError> {
107 let row = sqlx::query(&format!(
108 r#"
109 SELECT {}
110 FROM mounts
111 JOIN node_mounts ON mounts.uuid = node_mounts.mount_uuid
112 JOIN nest_egg_mounts ON mounts.uuid = nest_egg_mounts.mount_uuid
113 WHERE node_mounts.node_uuid = $1 AND nest_egg_mounts.egg_uuid = $2 AND mounts.uuid = $3
114 "#,
115 Self::columns_sql(None)
116 ))
117 .bind(node_uuid)
118 .bind(egg_uuid)
119 .bind(uuid)
120 .fetch_optional(database.read())
121 .await?;
122
123 row.try_map(|row| Self::map(None, &row))
124 }
125
126 pub async fn all_with_pagination(
127 database: &crate::database::Database,
128 page: i64,
129 per_page: i64,
130 search: Option<&str>,
131 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
132 let offset = (page - 1) * per_page;
133
134 let rows = sqlx::query(&format!(
135 r#"
136 SELECT {}, COUNT(*) OVER() AS total_count
137 FROM mounts
138 WHERE ($1 IS NULL OR mounts.name ILIKE '%' || $1 || '%')
139 ORDER BY mounts.created
140 LIMIT $2 OFFSET $3
141 "#,
142 Self::columns_sql(None)
143 ))
144 .bind(search)
145 .bind(per_page)
146 .bind(offset)
147 .fetch_all(database.read())
148 .await?;
149
150 Ok(super::Pagination {
151 total: rows
152 .first()
153 .map_or(Ok(0), |row| row.try_get("total_count"))?,
154 per_page,
155 page,
156 data: rows
157 .into_iter()
158 .map(|row| Self::map(None, &row))
159 .try_collect_vec()?,
160 })
161 }
162}
163
164#[async_trait::async_trait]
165impl IntoAdminApiObject for Mount {
166 type AdminApiObject = AdminApiMount;
167 type ExtraArgs<'a> = ();
168
169 async fn into_admin_api_object<'a>(
170 self,
171 state: &crate::State,
172 _args: Self::ExtraArgs<'a>,
173 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
174 let api_object = AdminApiMount::init_hooks(&self, state).await?;
175
176 let api_object = finish_extendible!(
177 AdminApiMount {
178 uuid: self.uuid,
179 name: self.name,
180 description: self.description,
181 source: self.source,
182 target: self.target,
183 read_only: self.read_only,
184 user_mountable: self.user_mountable,
185 created: self.created.and_utc(),
186 },
187 api_object,
188 state
189 )?;
190
191 Ok(api_object)
192 }
193}
194
195#[async_trait::async_trait]
196impl ByUuid for Mount {
197 async fn by_uuid(
198 database: &crate::database::Database,
199 uuid: uuid::Uuid,
200 ) -> Result<Self, crate::database::DatabaseError> {
201 let row = sqlx::query(&format!(
202 r#"
203 SELECT {}
204 FROM mounts
205 WHERE mounts.uuid = $1
206 "#,
207 Self::columns_sql(None)
208 ))
209 .bind(uuid)
210 .fetch_one(database.read())
211 .await?;
212
213 Self::map(None, &row)
214 }
215
216 async fn by_uuid_with_transaction(
217 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
218 uuid: uuid::Uuid,
219 ) -> Result<Self, crate::database::DatabaseError> {
220 let row = sqlx::query(&format!(
221 r#"
222 SELECT {}
223 FROM mounts
224 WHERE mounts.uuid = $1
225 "#,
226 Self::columns_sql(None)
227 ))
228 .bind(uuid)
229 .fetch_one(&mut **transaction)
230 .await?;
231
232 Self::map(None, &row)
233 }
234}
235
236#[derive(ToSchema, Deserialize, Validate)]
237pub struct CreateMountOptions {
238 #[garde(length(chars, min = 1, max = 255))]
239 #[schema(min_length = 1, max_length = 255)]
240 pub name: compact_str::CompactString,
241 #[garde(length(chars, min = 1, max = 1024))]
242 #[schema(min_length = 1, max_length = 1024)]
243 pub description: Option<compact_str::CompactString>,
244 #[garde(length(chars, min = 1, max = 255))]
245 #[schema(min_length = 1, max_length = 255)]
246 pub source: compact_str::CompactString,
247 #[garde(length(chars, min = 1, max = 255))]
248 #[schema(min_length = 1, max_length = 255)]
249 pub target: compact_str::CompactString,
250 #[garde(skip)]
251 pub read_only: bool,
252 #[garde(skip)]
253 pub user_mountable: bool,
254}
255
256#[async_trait::async_trait]
257impl CreatableModel for Mount {
258 type CreateOptions<'a> = CreateMountOptions;
259 type CreateResult = Self;
260
261 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
262 static CREATE_LISTENERS: LazyLock<CreateListenerList<Mount>> =
263 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
264
265 &CREATE_LISTENERS
266 }
267
268 async fn create_with_transaction(
269 state: &crate::State,
270 mut options: Self::CreateOptions<'_>,
271 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
272 ) -> Result<Self, crate::database::DatabaseError> {
273 options.validate()?;
274
275 let mut query_builder = InsertQueryBuilder::new("mounts");
276
277 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
278
279 query_builder
280 .set("name", &options.name)
281 .set("description", &options.description)
282 .set("source", &options.source)
283 .set("target", &options.target)
284 .set("read_only", options.read_only)
285 .set("user_mountable", options.user_mountable);
286
287 let row = query_builder
288 .returning(&Self::columns_sql(None))
289 .fetch_one(&mut **transaction)
290 .await?;
291 let mut mount = Self::map(None, &row)?;
292
293 Self::run_after_create_handlers(&mut mount, &options, state, transaction).await?;
294
295 Ok(mount)
296 }
297}
298
299#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
300pub struct UpdateMountOptions {
301 #[garde(length(chars, min = 1, max = 255))]
302 #[schema(min_length = 1, max_length = 255)]
303 pub name: Option<compact_str::CompactString>,
304 #[garde(length(chars, min = 1, max = 1024))]
305 #[schema(min_length = 1, max_length = 1024)]
306 #[serde(
307 default,
308 skip_serializing_if = "Option::is_none",
309 with = "::serde_with::rust::double_option"
310 )]
311 pub description: Option<Option<compact_str::CompactString>>,
312 #[garde(length(chars, min = 1, max = 255))]
313 #[schema(min_length = 1, max_length = 255)]
314 pub source: Option<compact_str::CompactString>,
315 #[garde(length(chars, min = 1, max = 255))]
316 #[schema(min_length = 1, max_length = 255)]
317 pub target: Option<compact_str::CompactString>,
318 #[garde(skip)]
319 pub read_only: Option<bool>,
320 #[garde(skip)]
321 pub user_mountable: Option<bool>,
322}
323
324#[async_trait::async_trait]
325impl UpdatableModel for Mount {
326 type UpdateOptions = UpdateMountOptions;
327
328 fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
329 static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<Mount>> =
330 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
331
332 &UPDATE_LISTENERS
333 }
334
335 async fn update_with_transaction(
336 &mut self,
337 state: &crate::State,
338 mut options: Self::UpdateOptions,
339 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
340 ) -> Result<(), crate::database::DatabaseError> {
341 options.validate()?;
342
343 let mut query_builder = UpdateQueryBuilder::new("mounts");
344
345 self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
346 .await?;
347
348 query_builder
349 .set("name", options.name.as_ref())
350 .set(
351 "description",
352 options.description.as_ref().map(|d| d.as_ref()),
353 )
354 .set("source", options.source.as_ref())
355 .set("target", options.target.as_ref())
356 .set("read_only", options.read_only)
357 .set("user_mountable", options.user_mountable)
358 .where_eq("uuid", self.uuid);
359
360 query_builder.execute(&mut **transaction).await?;
361
362 if let Some(name) = options.name {
363 self.name = name;
364 }
365 if let Some(description) = options.description {
366 self.description = description;
367 }
368 if let Some(source) = options.source {
369 self.source = source;
370 }
371 if let Some(target) = options.target {
372 self.target = target;
373 }
374 if let Some(read_only) = options.read_only {
375 self.read_only = read_only;
376 }
377 if let Some(user_mountable) = options.user_mountable {
378 self.user_mountable = user_mountable;
379 }
380
381 self.run_after_update_handlers(state, transaction).await?;
382
383 Ok(())
384 }
385}
386
387#[async_trait::async_trait]
388impl DeletableModel for Mount {
389 type DeleteOptions = ();
390
391 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
392 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<Mount>> =
393 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
394
395 &DELETE_LISTENERS
396 }
397
398 async fn delete_with_transaction(
399 &self,
400 state: &crate::State,
401 options: Self::DeleteOptions,
402 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
403 ) -> Result<(), anyhow::Error> {
404 self.run_delete_handlers(&options, state, transaction)
405 .await?;
406
407 sqlx::query(
408 r#"
409 DELETE FROM mounts
410 WHERE mounts.uuid = $1
411 "#,
412 )
413 .bind(self.uuid)
414 .execute(&mut **transaction)
415 .await?;
416
417 self.run_after_delete_handlers(&options, state, transaction)
418 .await?;
419
420 Ok(())
421 }
422}
423
424#[schema_extension_derive::extendible]
425#[init_args(Mount, crate::State)]
426#[hook_args(crate::State)]
427#[derive(ToSchema, Serialize)]
428#[schema(title = "Mount")]
429pub struct AdminApiMount {
430 pub uuid: uuid::Uuid,
431
432 pub name: compact_str::CompactString,
433 pub description: Option<compact_str::CompactString>,
434
435 pub source: compact_str::CompactString,
436 pub target: compact_str::CompactString,
437
438 pub read_only: bool,
439 pub user_mountable: bool,
440
441 pub created: chrono::DateTime<chrono::Utc>,
442}