Skip to main content

shared/models/
node_mount.rs

1use crate::{models::InsertQueryBuilder, prelude::*};
2use garde::Validate;
3use serde::{Deserialize, Serialize};
4use sqlx::{Row, postgres::PgRow};
5use std::{
6    collections::BTreeMap,
7    sync::{Arc, LazyLock},
8};
9use utoipa::ToSchema;
10
11#[derive(Serialize, Deserialize)]
12pub struct NodeMount {
13    pub mount: Fetchable<super::mount::Mount>,
14    pub node: Fetchable<super::node::Node>,
15
16    pub created: chrono::NaiveDateTime,
17
18    extension_data: super::ModelExtensionData,
19}
20
21impl BaseModel for NodeMount {
22    const NAME: &'static str = "node_mount";
23
24    fn get_extension_list() -> &'static super::ModelExtensionList {
25        static EXTENSIONS: LazyLock<super::ModelExtensionList> =
26            LazyLock::new(|| std::sync::RwLock::new(Vec::new()));
27
28        &EXTENSIONS
29    }
30
31    fn get_extension_data(&self) -> &super::ModelExtensionData {
32        &self.extension_data
33    }
34
35    #[inline]
36    fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
37        let prefix = prefix.unwrap_or_default();
38
39        BTreeMap::from([
40            (
41                "node_mounts.mount_uuid",
42                compact_str::format_compact!("{prefix}mount_uuid"),
43            ),
44            (
45                "node_mounts.node_uuid",
46                compact_str::format_compact!("{prefix}node_uuid"),
47            ),
48            (
49                "node_mounts.created",
50                compact_str::format_compact!("{prefix}created"),
51            ),
52        ])
53    }
54
55    #[inline]
56    fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
57        let prefix = prefix.unwrap_or_default();
58
59        Ok(Self {
60            mount: super::mount::Mount::get_fetchable(
61                row.try_get(compact_str::format_compact!("{prefix}mount_uuid").as_str())?,
62            ),
63            node: super::node::Node::get_fetchable(
64                row.try_get(compact_str::format_compact!("{prefix}node_uuid").as_str())?,
65            ),
66            created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
67            extension_data: Self::map_extensions(prefix, row)?,
68        })
69    }
70}
71
72impl NodeMount {
73    pub async fn by_node_uuid_mount_uuid(
74        database: &crate::database::Database,
75        node_uuid: uuid::Uuid,
76        mount_uuid: uuid::Uuid,
77    ) -> Result<Option<Self>, crate::database::DatabaseError> {
78        let row = sqlx::query(&format!(
79            r#"
80            SELECT {}
81            FROM node_mounts
82            WHERE node_mounts.node_uuid = $1 AND node_mounts.mount_uuid = $2
83            "#,
84            Self::columns_sql(None)
85        ))
86        .bind(node_uuid)
87        .bind(mount_uuid)
88        .fetch_optional(database.read())
89        .await?;
90
91        row.try_map(|row| Self::map(None, &row))
92    }
93
94    pub async fn by_node_uuid_with_pagination(
95        database: &crate::database::Database,
96        node_uuid: uuid::Uuid,
97        page: i64,
98        per_page: i64,
99        search: Option<&str>,
100    ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
101        let offset = (page - 1) * per_page;
102
103        let rows = sqlx::query(&format!(
104            r#"
105            SELECT {}, COUNT(*) OVER() AS total_count
106            FROM node_mounts
107            JOIN mounts ON mounts.uuid = node_mounts.mount_uuid
108            WHERE node_mounts.node_uuid = $1 AND ($2 IS NULL OR mounts.name ILIKE '%' || $2 || '%')
109            ORDER BY node_mounts.created
110            LIMIT $3 OFFSET $4
111            "#,
112            Self::columns_sql(None)
113        ))
114        .bind(node_uuid)
115        .bind(search)
116        .bind(per_page)
117        .bind(offset)
118        .fetch_all(database.read())
119        .await?;
120
121        Ok(super::Pagination {
122            total: rows
123                .first()
124                .map_or(Ok(0), |row| row.try_get("total_count"))?,
125            per_page,
126            page,
127            data: rows
128                .into_iter()
129                .map(|row| Self::map(None, &row))
130                .try_collect_vec()?,
131        })
132    }
133
134    pub async fn by_mount_uuid_with_pagination(
135        database: &crate::database::Database,
136        mount_uuid: uuid::Uuid,
137        page: i64,
138        per_page: i64,
139        search: Option<&str>,
140    ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
141        let offset = (page - 1) * per_page;
142
143        let rows = sqlx::query(&format!(
144            r#"
145            SELECT {}, COUNT(*) OVER() AS total_count
146            FROM node_mounts
147            JOIN nodes ON nodes.uuid = node_mounts.node_uuid
148            WHERE node_mounts.mount_uuid = $1 AND ($2 IS NULL OR nodes.name ILIKE '%' || $2 || '%')
149            ORDER BY node_mounts.created
150            LIMIT $3 OFFSET $4
151            "#,
152            Self::columns_sql(None)
153        ))
154        .bind(mount_uuid)
155        .bind(search)
156        .bind(per_page)
157        .bind(offset)
158        .fetch_all(database.read())
159        .await?;
160
161        Ok(super::Pagination {
162            total: rows
163                .first()
164                .map_or(Ok(0), |row| row.try_get("total_count"))?,
165            per_page,
166            page,
167            data: rows
168                .into_iter()
169                .map(|row| Self::map(None, &row))
170                .try_collect_vec()?,
171        })
172    }
173
174    #[inline]
175    pub async fn into_admin_node_api_object(
176        self,
177        state: &crate::State,
178        _args: (),
179    ) -> Result<AdminApiNodeNodeMount, crate::database::DatabaseError> {
180        Ok(AdminApiNodeNodeMount {
181            node: self
182                .node
183                .fetch_cached(&state.database)
184                .await?
185                .into_admin_api_object(state, ())
186                .await?,
187            created: self.created.and_utc(),
188        })
189    }
190}
191
192#[async_trait::async_trait]
193impl IntoAdminApiObject for NodeMount {
194    type AdminApiObject = AdminApiNodeMount;
195    type ExtraArgs<'a> = ();
196
197    async fn into_admin_api_object<'a>(
198        self,
199        state: &crate::State,
200        _args: Self::ExtraArgs<'a>,
201    ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
202        let api_object = AdminApiNodeMount::init_hooks(&self, state).await?;
203
204        let api_object = finish_extendible!(
205            AdminApiNodeMount {
206                mount: self
207                    .mount
208                    .fetch_cached(&state.database)
209                    .await?
210                    .into_admin_api_object(state, ())
211                    .await?,
212                created: self.created.and_utc(),
213            },
214            api_object,
215            state
216        )?;
217
218        Ok(api_object)
219    }
220}
221
222#[derive(ToSchema, Deserialize, Validate)]
223pub struct CreateNodeMountOptions {
224    #[garde(skip)]
225    pub node_uuid: uuid::Uuid,
226    #[garde(skip)]
227    pub mount_uuid: uuid::Uuid,
228}
229
230#[async_trait::async_trait]
231impl CreatableModel for NodeMount {
232    type CreateOptions<'a> = CreateNodeMountOptions;
233    type CreateResult = Self;
234
235    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
236        static CREATE_LISTENERS: LazyLock<CreateListenerList<NodeMount>> =
237            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
238
239        &CREATE_LISTENERS
240    }
241
242    async fn create_with_transaction(
243        state: &crate::State,
244        mut options: Self::CreateOptions<'_>,
245        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
246    ) -> Result<Self, crate::database::DatabaseError> {
247        options.validate()?;
248
249        super::mount::Mount::by_uuid_optional_cached(&state.database, options.mount_uuid)
250            .await?
251            .ok_or(crate::database::InvalidRelationError("mount"))?;
252
253        let mut query_builder = InsertQueryBuilder::new("node_mounts");
254
255        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
256
257        query_builder
258            .set("node_uuid", options.node_uuid)
259            .set("mount_uuid", options.mount_uuid);
260
261        let row = query_builder
262            .returning(&Self::columns_sql(None))
263            .fetch_one(&mut **transaction)
264            .await?;
265        let mut node_mount = Self::map(None, &row)?;
266
267        Self::run_after_create_handlers(&mut node_mount, &options, state, transaction).await?;
268
269        Ok(node_mount)
270    }
271}
272
273#[async_trait::async_trait]
274impl DeletableModel for NodeMount {
275    type DeleteOptions = ();
276
277    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
278        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<NodeMount>> =
279            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
280
281        &DELETE_LISTENERS
282    }
283
284    async fn delete_with_transaction(
285        &self,
286        state: &crate::State,
287        options: Self::DeleteOptions,
288        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
289    ) -> Result<(), anyhow::Error> {
290        self.run_delete_handlers(&options, state, transaction)
291            .await?;
292
293        sqlx::query(
294            r#"
295            DELETE FROM node_mounts
296            WHERE node_mounts.node_uuid = $1 AND node_mounts.mount_uuid = $2
297            "#,
298        )
299        .bind(self.node.uuid)
300        .bind(self.mount.uuid)
301        .execute(&mut **transaction)
302        .await?;
303
304        self.run_after_delete_handlers(&options, state, transaction)
305            .await?;
306
307        Ok(())
308    }
309}
310
311#[derive(ToSchema, Serialize)]
312#[schema(title = "AdminNodeNodeMount")]
313pub struct AdminApiNodeNodeMount {
314    pub node: super::node::AdminApiNode,
315
316    pub created: chrono::DateTime<chrono::Utc>,
317}
318
319#[schema_extension_derive::extendible]
320#[init_args(NodeMount, crate::State)]
321#[hook_args(crate::State)]
322#[derive(ToSchema, Serialize)]
323#[schema(title = "AdminNodeMount")]
324pub struct AdminApiNodeMount {
325    pub mount: super::mount::AdminApiMount,
326
327    pub created: chrono::DateTime<chrono::Utc>,
328}