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 NodeDevice {
13 pub device: Fetchable<super::device::Device>,
14 pub node: Fetchable<super::node::Node>,
15
16 pub created: chrono::NaiveDateTime,
17
18 extension_data: super::ModelExtensionData,
19}
20
21impl BaseModel for NodeDevice {
22 const NAME: &'static str = "node_device";
23
24 fn get_extension_list() -> &'static super::ModelExtensionList {
25 static EXTENSIONS: LazyLock<super::ModelExtensionList> =
26 LazyLock::new(|| parking_lot::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_devices.device_uuid",
42 compact_str::format_compact!("{prefix}device_uuid"),
43 ),
44 (
45 "node_devices.node_uuid",
46 compact_str::format_compact!("{prefix}node_uuid"),
47 ),
48 (
49 "node_devices.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 device: super::device::Device::get_fetchable(
61 row.try_get(compact_str::format_compact!("{prefix}device_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 NodeDevice {
73 pub async fn by_node_uuid_device_uuid(
74 database: &crate::database::Database,
75 node_uuid: uuid::Uuid,
76 device_uuid: uuid::Uuid,
77 ) -> Result<Option<Self>, crate::database::DatabaseError> {
78 let row = sqlx::query(sqlx::AssertSqlSafe(format!(
79 r#"
80 SELECT {}
81 FROM node_devices
82 WHERE node_devices.node_uuid = $1 AND node_devices.device_uuid = $2
83 "#,
84 Self::columns_sql(None)
85 )))
86 .bind(node_uuid)
87 .bind(device_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 super::PaginatedQueryBuilder::new(
102 Self::columns_sql(None),
103 format!(
104 r#"
105 FROM node_devices
106 JOIN devices ON devices.uuid = node_devices.device_uuid
107 WHERE node_devices.node_uuid = $1 AND {search}
108 "#,
109 search = super::search_sql(2, &["devices.name"], &["devices.uuid"]),
110 ),
111 "node_devices.created, node_devices.node_uuid, node_devices.device_uuid",
112 )
113 .bind(node_uuid)
114 .bind(search)
115 .fetch(database, page, per_page)
116 .await
117 }
118
119 pub async fn by_device_uuid_with_pagination(
120 database: &crate::database::Database,
121 device_uuid: uuid::Uuid,
122 page: i64,
123 per_page: i64,
124 search: Option<&str>,
125 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
126 super::PaginatedQueryBuilder::new(
127 Self::columns_sql(None),
128 format!(
129 r#"
130 FROM node_devices
131 JOIN nodes ON nodes.uuid = node_devices.node_uuid
132 WHERE node_devices.device_uuid = $1 AND {search}
133 "#,
134 search = super::search_sql(2, &["nodes.name"], &["nodes.uuid"]),
135 ),
136 "node_devices.created, node_devices.node_uuid, node_devices.device_uuid",
137 )
138 .bind(device_uuid)
139 .bind(search)
140 .fetch(database, page, per_page)
141 .await
142 }
143
144 #[inline]
145 pub async fn into_admin_node_api_object(
146 self,
147 state: &crate::State,
148 _args: (),
149 ) -> Result<AdminApiNodeNodeDevice, crate::database::DatabaseError> {
150 Ok(AdminApiNodeNodeDevice {
151 node: self
152 .node
153 .fetch_cached(&state.database)
154 .await?
155 .into_admin_api_object(state, ())
156 .await?,
157 created: self.created.and_utc(),
158 })
159 }
160}
161
162#[async_trait::async_trait]
163impl IntoAdminApiObject for NodeDevice {
164 type AdminApiObject = AdminApiNodeDevice;
165 type ExtraArgs<'a> = ();
166
167 async fn into_admin_api_object<'a>(
168 self,
169 state: &crate::State,
170 _args: Self::ExtraArgs<'a>,
171 ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
172 let api_object = AdminApiNodeDevice::init_hooks(&self, state).await?;
173
174 let api_object = finish_extendible!(
175 AdminApiNodeDevice {
176 device: self
177 .device
178 .fetch_cached(&state.database)
179 .await?
180 .into_admin_api_object(state, ())
181 .await?,
182 created: self.created.and_utc(),
183 },
184 api_object,
185 state
186 )?;
187
188 Ok(api_object)
189 }
190}
191
192#[derive(ToSchema, Deserialize, Validate)]
193pub struct CreateNodeDeviceOptions {
194 #[garde(skip)]
195 pub node_uuid: uuid::Uuid,
196 #[garde(skip)]
197 pub device_uuid: uuid::Uuid,
198}
199
200#[async_trait::async_trait]
201impl CreatableModel for NodeDevice {
202 type CreateOptions<'a> = CreateNodeDeviceOptions;
203 type CreateResult = Self;
204
205 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
206 static CREATE_LISTENERS: LazyLock<CreateListenerList<NodeDevice>> =
207 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
208
209 &CREATE_LISTENERS
210 }
211
212 async fn create_with_transaction(
213 state: &crate::State,
214 mut options: Self::CreateOptions<'_>,
215 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
216 ) -> Result<Self, crate::database::DatabaseError> {
217 options.validate()?;
218
219 super::device::Device::by_uuid_optional_cached(&state.database, options.device_uuid)
220 .await?
221 .ok_or(crate::database::InvalidRelationError("device"))?;
222
223 let mut query_builder = InsertQueryBuilder::new("node_devices");
224
225 Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
226
227 query_builder
228 .set("node_uuid", options.node_uuid)
229 .set("device_uuid", options.device_uuid);
230
231 let row = query_builder
232 .returning(&Self::columns_sql(None))
233 .fetch_one(&mut **transaction)
234 .await?;
235 let mut node_device = Self::map(None, &row)?;
236
237 Self::run_after_create_handlers(&mut node_device, &options, state, transaction).await?;
238
239 Ok(node_device)
240 }
241}
242
243#[async_trait::async_trait]
244impl DeletableModel for NodeDevice {
245 type DeleteOptions = ();
246
247 fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
248 static DELETE_LISTENERS: LazyLock<DeleteHandlerList<NodeDevice>> =
249 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
250
251 &DELETE_LISTENERS
252 }
253
254 async fn delete_with_transaction(
255 &self,
256 state: &crate::State,
257 options: Self::DeleteOptions,
258 transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
259 ) -> Result<(), anyhow::Error> {
260 self.run_delete_handlers(&options, state, transaction)
261 .await?;
262
263 sqlx::query(
264 r#"
265 DELETE FROM node_devices
266 WHERE node_devices.node_uuid = $1 AND node_devices.device_uuid = $2
267 "#,
268 )
269 .bind(self.node.uuid)
270 .bind(self.device.uuid)
271 .execute(&mut **transaction)
272 .await?;
273
274 self.run_after_delete_handlers(&options, state, transaction)
275 .await?;
276
277 Ok(())
278 }
279}
280
281#[derive(ToSchema, Serialize)]
282#[schema(title = "AdminNodeNodeDevice")]
283pub struct AdminApiNodeNodeDevice {
284 pub node: super::node::AdminApiNode,
285
286 pub created: chrono::DateTime<chrono::Utc>,
287}
288
289#[schema_extension_derive::extendible]
290#[init_args(NodeDevice, crate::State)]
291#[hook_args(crate::State)]
292#[derive(ToSchema, Serialize)]
293#[schema(title = "AdminNodeDevice")]
294pub struct AdminApiNodeDevice {
295 pub device: super::device::AdminApiDevice,
296
297 pub created: chrono::DateTime<chrono::Utc>,
298}