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}